- 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.