- Feature Name: `bool_not` - Start Date: 2021-01-29 - RFC PR: TBD <!-- [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) --> - Rust Issue: TBD <!-- [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) --> # Summary [summary]: #summary Adding inherent `.not()` method to `bool` type that matches the behavior of logical negation operation `!bool`. # Introduction [introduction]: #introduction A thread in internal methods proposing adding "[is_not_empty() as an alternative for !is_empty()][irl_not_empty]". One idea that people often suggest is adding an inherent `.not()` method directly on `bool` type [^1][^2]. [irl_not_empty]: https://internals.rust-lang.org/t/the-is-not-empty-method-as-more-clearly-alternative-for-is-empty/10612 [^1]: https://internals.rust-lang.org/t/the-is-not-empty-method-as-more-clearly-alternative-for-is-empty/10612/4 [^2]: https://internals.rust-lang.org/t/the-is-not-empty-method-as-more-clearly-alternative-for-is-empty/10612/8 # Motivation [motivation]: #motivation <!-- ### Why are we doing this? What use cases does it support? What is the expected outcome? --> There are some complaints about the use of `!` sigil as the logical negation operator: * `!` is easy to miss. People often have to double look at calls like `if vec.is_empty() {}` to be sure they don't miss the `!` sigil. This might slow down reading code speed. * The precedence of `!` requires extra care when used in long expressions like `!requests.params.config.key.value.filter(|o| ...).map(|o| ...).is_empty()`. Adding `.not()` method to `bool` type would allow people to use `vec.is_empty().not()`. Now the logical negation operation is hard to miss. People don't need to importing trait `core::ops::Not` for every module they would like to use this method. # Guide-level explanation [guide-level-explanation]: #guide-level-explanation The builtin boolean type `bool` now contains an inherent method `not()` that represents [logical negation operation][wiki-negation]. Now people are recommended to use `bool.not()` instead of `!bool`. [wiki-negation]: https://en.wikipedia.org/wiki/Negation # Reference-level explanation [reference-level-explanation]: #reference-level-explanation <!-- - Its interaction with other features is clear. --> The RFC is very easy to implemented: ```rust #[lang = "bool"] impl bool { pub fn not(self) -> bool { !self } } ``` That would allow us to use `<boolean expression>.not()` directly without importing `Not` trait. For example: Instead of `!vec.is_empty()`, not we go with `vec.is_empty().not()`. `vec.is_empty().not()` is readable, but some people may find them funny[^read-funny]. [^read-funny]: https://internals.rust-lang.org/t/the-is-not-empty-method-as-more-clearly-alternative-for-is-empty/10612/4?u=lzutao The inherent `bool::not()` method should not interact badly with `T: Not` as proved here: <https://rust.godbolt.org/z/WMK3ss>. Compiler should prefer inherent `.not` method over `.not` from `core::ops::Not` trait. <!-- - Corner cases are dissected by example. The section should return to the examples given in the previous section, and explain more fully how the detailed proposal makes those examples work. *write more here...* --> # Drawbacks [drawbacks]: #drawbacks Why should we *not* do this? - [ ] Programmers coming from C-like languages that get used to ``!`` sigil. Now they have to transit over `.not()` method. - [ ] More than one way to do logical negation operation. This might be addressed by a lint proposed in [Future possibilities][future-possibilities] part. - [ ] Existing Rust code has to transit to this new style. This could be addressed by adding an auto-suggestion in the rustfix tool. - [ ] This requires people to type more than just `!` (1 character) and now ``.not()`` (5 characters). - [ ] Minor regression in runtime performance of debug build. # Rationale and alternatives [rationale-and-alternatives]: #rationale-and-alternatives ### Why is this design the best in the space of possible designs? Adding `.not()` method directly to `bool` requires very little changes ([3 lines of code excluding documentation][pr]) to the standard library. It would solve the readability problem caused by using `!` in boolean expressions. [pr]: https://github.com/rust-lang/rust/compare/master...lzutao:not ### What is the impact of not doing this? People continue to use the `!` sigil, which might cause subtle bugs because of missing/forgetting to notice that tiny sigil. People that prefer to use `.not()` method have to import `core::ops::Not` in every module they want to use it. ### What other designs have been considered and what is the rationale for not choosing them? - [ ] Doing nothing. Rust copied this near-invisible negation sigil from C to make it familiar with C-like programmers. If people find code hard to read, they could: * use `false == boolean` or `boolean != true` instead. * add separate methods for negation on every collections. For example, in the case of `vec.is_empty()`, we could add `Vec::not_empty()` or `Vec::is_not_empty()` or `Vec::has_items()` and use that instead. Howevery this again requires care about `!vec.is_not_empty()` calls and the likes. - [ ] Linting to make people prefer using spaces before `!`, for example `! true`. This is also a good alternative. However, this might be prior art for other space-sensitive syntax additions in the future. - [ ] Making `core::ops::Not` be a part of the prelude. This is actually quite a good alternative. However, this will allow using `.not` more than just on `bool` type, which might cause more problems with existing code. - [ ] Allowing to use `~` as alternative for `!`: `~true`. `~` More readable than `!`. However, `~` is not very popular as the logical negation operator in other programming languages either. - [ ] Adding postfix *not* operator: `true.! == !true == false`. But this might be the same as using `true.not()` directly. - [ ] Adding postfix `.not!` macro. Might be easier by just using `true.not()`. This also requires adding postfix macro support to the language. - [ ] Adding free `not` function. Something like `core::ops::not(true)`. This requires people to import that function to conveniently use it. - [ ] Adding `.nicht()` or `.sike()` instead of `.not()` ([suggested by CAD97][sike]). - [ ] Adding `not` as a prefix keyword in new edition. This might cause a lot more churns than this RFC. Also `if not a && b` is not quite clear if `not` covers both `a && b` or just `a`. - [ ] Adding `¬` sigil (used in logic languages), but the preferred trend is to not adding more sigils. Another disadvantage is that this symbol is not easy to type on normal keyboards. - [ ] Adding `unless` keyword. This might really harm code readability [^unless1][^unless2]. [sike]: https://internals.rust-lang.org/t/the-is-not-empty-method-as-more-clearly-alternative-for-is-empty/10612/15 [^unless1]: https://internals.rust-lang.org/t/the-is-not-empty-method-as-more-clearly-alternative-for-is-empty/10612/111 [^unless2]: https://internals.rust-lang.org/t/the-is-not-empty-method-as-more-clearly-alternative-for-is-empty/10612/26?u=lzutao # Prior art [prior-art]: #prior-art The Rust language team doesn't want to accept new sigils for features. Adding `bool::not()` is not against that goal/trend. Here is the table for the syntax used for logical negation in some programming languages[^info]: | Language | Sigil/keyword | Example | | --------------------------------------------------------------------- | ---------------------- | --------------------- | | ALGOL 68 | `NOT` (or `¬` [^algo]) | `NOT true` (`¬ true`) | | APL | `~` | `~0` | | C / C++ / C# / D / Go / Java / Swift / Zig / PHP / JavaScript / Scala | `!` | `!true` | | Common Lisp | `not` | `not a` | | Python / Eiffel / Haskell | `not` | `not True` | | Ocaml / Erlang | `not` | `not true` | | Perl / Raku | `not` or `!` | `not true` or `!true` | | Pascal | `not` | `not(true)` | | Ruby | `!` | `!a` | | Scheme | `not` | `not a` | | PL/I | `^` | `^'0'b` | | Prolog | `\+` | `\+false` | | Seed7 | `not` | `not TRUE` | | Smalltalk | `not` | `true not` | | Visual Basic .NET | `Not` | `Not True` | We could add a new keyword `not` (for example `not true`). But adding a new keyword requires heavier changes in compiler and language syntax. It also requires new addition to take benefits of improving readability. Old Rust editions have to continue to use `!` syntax. This leaves us with the options of an inherent ``.not`` method on the boolean type, which all editions could start to use. Note that there is ``.await`` (in `future.await`) used as a postfix keyword instead of `await Task` in C#. [^algo]: For a European 8 bit/byte character set, for example, "ALCOR" or "GOST ¢" [^info]: Information gathered in Wiki and Rosettacode pages: https://rosettacode.org/wiki/Logical_operations and https://rosettacode.org/wiki/Boolean_values # Unresolved questions [unresolved-questions]: #unresolved-questions - Does `bool::not()` actually interact soundly with `Not` trait ? - Do we need to lint against using `!bool` and guide people to use `bool.not()` instead? # Future possibilities [future-possibilities]: #future-possibilities - [ ] Lint against using `!bool` and guide people to use `bool.not()` instead. - [ ] rustdoc or rustfix could help people transit old code to new style.