[pre-RFC] Adding `bool::not()` method

Updated: There is a new-prelude RFC in which the author proposes not to add Not trait to the prelude but favors the approach described here.


This RFC proposes adding inherent .not() method to bool type that matches the behavior of logical negation operation !bool.

Please read more on HackMD: https://hackmd.io/@lzutao/r1Mzcx-eO.

Everyone feels free to comment here or on the HackMD link above. You could make changes to the RFC while sign-in (with GitHub ID or so) in the above link.

P/S: I am not a native English speaker nor a good writer. So please bear with me.

cc The is_not_empty() method as more clearly alternative for !is_empty()

4 Likes

Just use std::ops::Not;

14 Likes

All logical operations are defined by traits in std::ops.

As an alternative, adding std::ops::Not to the prelude was proposed for the next edition.

20 Likes

As I wrote in the alternative sections, adding Not trait to the prelude is also a good alternative. However, this makes not method accessible in more types than just bool, which might cause bigger breakages and problems than just this RFC.

Also, I don't think adding trait could be limited to edition boundary, do it ? If it is really limited, what about old Rust editions ?

1 Like

Minor changes to the standard library usually don't require an RFC, do they?

Besides, if an inherent not method is added to bool, nobody is forced to use it, so the drawback "Existing Rust code has to transit to this new style" doesn't apply.

This RFC is small, however it is highly controversial. On one side, some people feels that ! is enough. On the other hand, people finds that ! is easy to miss. So changes like this ends up being time-consuming via RFCs.

Then we would have two ways to do logical negation. The code would likely mix both styles, which could be code smell. So there should be a lint to drive people over the new style. This is the drawback.

4 Likes

There have always been multiple ways for that. This method just makes one of them more convenient (it's no longer required to import the Not trait).

I don't think one of these ways must be preferred and the other one deprecated. It's okay if there are multiple ways of doing things. Like creating a default value, which can be done with Foo::default(), Default::default() or (on nightly) just default(). Or wrapping arithmetic, which can be done either with the Wrapping type or the wrapping_* methods.

A third option is to add inherent traits to the language. This way neither the duplicating inherent method, nor the (arguably) weird extension of the prelude will be needed.

8 Likes

That's an interesting idea. But the lang team discussion about it is unclear. Also, as described in that RFC, it is not a breaking change to change from inherent bool::not() to use the inherent trait feature, which generates the same code. Hence, I think "inherent trait" doesn't block this RFC.

1 Like

I do think ! at the front is easy to miss but .not() does not seemed to improve a lot while linting with clippy probably since .not() will be on another line if the line is too long when being formatted. For example (I just made it shorter),

if a
    .b()
    .c()
    .not() {

Previously although it does not look weird but it makes it easy to miss

if !a
    .b()
    .c() {

I think a postfix syntactic sugar .not may look better. Since it is rare to have stuff after .not (usually it is the terminator unless they want some operation on bool).

if a
    .b()
    .c().not {
}

Not sure if that's the case or maybe having .not() on its own line look better? If in that case people could still set a variable and do the current method right? Some more it may be clearer what is being negated since it was given a name.

let x = a
    .b()
    .c();
if !x {

That is entirely subjective.

It's also a difference of exactly 2 parentheses, so if .not() would end up on a new line, there is an extremely high probability that .not would also end up on a new line (that's also how it works with .await now). In addition, there are already existing solutions, what's the upside we get for spending this complexity budget? Yet another way to do what has already probably too many ways to accomplish?

For .await there was an actual use case. For this? I don't see it being worth the added complexity.

12 Likes

Just extend the .keyword proposal to operators as well:

if a
    .b()
    .c().! {
}

Could go even further:

a.b().c().!.if {
    let neg_five = 5.-;
    This is a comment .//
}
1 Like

That is lot more work plus I find the second one very unreadable.

1 Like

That one is a parsing nightmare if I ever saw one. And I would not consider comments as operators because they are treated very differently, and those differences start as early as parsing.

I understand why this is consistent with the proposal but I also agree with @Nokel81 that it can make code very difficult to read (especially because the example is a literal).

While we are at wild suggestions, why not add postfix operator sections, like x.log().(+ 1.0).exp()?

2 Likes

Why not just go the ruby/scala route and make operators (+, -, /) methods, allow symbols in methods, and allow method calls without parentheses?

(mostly joking)

I was joking too, no worries. I thought that would be implied by the ridiculous lengths to which I took it, lol.

2 Likes

Well, I failed to see the humour in it because I kind of like the concept actually. But it's certainly not worth spending Rust's already limited strangeness budget there.