• Feature Name: bool_not
  • Start Date: 2021-01-29
  • RFC PR: TBD
  • Rust Issue: TBD

Summary

Adding inherent .not() method to bool type that matches the behavior of logical negation operation !bool.

Introduction

A thread in internal methods proposing adding "is_not_empty() as an alternative for !is_empty()". One idea that people often suggest is adding an inherent .not() method directly on bool type [1][2].

Motivation

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

The builtin boolean type bool now contains an inherent method not() that represents logical negation operation. Now people are recommended to use bool.not() instead of !bool.

Reference-level explanation

The RFC is very easy to implemented:

#[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[3].

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.

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

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) to the standard library. It would solve the readability problem caused by using ! in boolean expressions.

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).
  • 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 [4][5].

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[6]:

Language Sigil/keyword Example
ALGOL 68 NOT (or ¬ [7]) 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#.

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

  • Lint against using !bool and guide people to use bool.not() instead.
  • rustdoc or rustfix could help people transit old code to new style.

  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 ↩︎

  3. https://internals.rust-lang.org/t/the-is-not-empty-method-as-more-clearly-alternative-for-is-empty/10612/4?u=lzutao ↩︎

  4. https://internals.rust-lang.org/t/the-is-not-empty-method-as-more-clearly-alternative-for-is-empty/10612/111 ↩︎

  5. https://internals.rust-lang.org/t/the-is-not-empty-method-as-more-clearly-alternative-for-is-empty/10612/26?u=lzutao ↩︎

  6. Information gathered in Wiki and Rosettacode pages: https://rosettacode.org/wiki/Logical_operations and https://rosettacode.org/wiki/Boolean_values ↩︎

  7. For a European 8 bit/byte character set, for example, "ALCOR" or "GOST ¢" ↩︎