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

It isn't possible to coerce anything to bool in Rust. The Not trait is implemented for numeric types, in which it returns the same type that goes in, and bool, which again returns itself.

5 Likes

To !is_empty(), or to is_not_empty(), that is the question:
Whether 'tis nobler in the in the mind to suffer
The ubiquity of sigils in the language,
Or to litter std with superfluous methods
And give Rustaceans cancer.

Nulliam Breakspeare

2 Likes

After having been following this thread for awhile I'm thinking that regardless of how this issue is ultimately solved one of the following should happen:

  • ops::Not should be part of the prelude.
  • bool should have a not() method even without the trait.
4 Likes

I think there's a strong bias for action in this thread.

The prefix-! operator exists and is certainly sufficient -- and something that's readily understandable by programmers in far more languages than just Rust. Anything else seems to me like it's actually a proposal to change conventions for how code is written, and thus should have an RFC talking about why something different here is valuable enough to overcome the cost of the churn from switching to a new way of doing things.

That's not to say that it's impossible, though. Personally, I certainly do prefer dataflow order, all else being equal.

7 Likes

I appreciate quoting for brevity but that snippet makes me sound a lot more strident than I was intending. That's my fault for being unclear.

For the record, I'm not advocating skipping the normal discussion process. I would however suggest that the disadvantages are few. The not() function is already defined as a core part of the language. This proposal isn't adding anything new in that regard. What it does do is make it more convenient to use directly than importing the trait to the top of every file.

There are potential disadvantages to each option I suggested but I don't think they require mass rewriting of code to use .not() instead of !. I guess there can be objections that there should be "only one way to do it" but I think that ship has already sailed in Rust.

1 Like

The most obvious example of which is imperative vs functional, such as loops vs iterators. Rust supports both.

1 Like

I also think adding a new sigil like this will hurt learnability - it’s one more operator / trait to learn that probably has its own set of rules / idiosyncrasies. I could also see it being a parse issue or a readability issue, even a safety issue.

I think the if _ in a syntax is a much better solution. First, it’s tried and true and already in Python, so there’s an existing model to use and for newcomers to use. Second, Rust already provides for x in a so if you remember one it’s intuitive to remember the other. This would still require a trait, but I vaguely recall coming across some trait, albeit I think in a third-party library, that provides the is_empty or contains method.

That being said, I’m a bit doubtful that !is_empty is so hard to read that it warrants a language extension just for it.

A middle ground here might be providing is_empty on a trait in the standard library, so somebody could provide their own is_not_empty method via generic impl if they really needed to.

This is the exact reason ExactSizeIterator::is_empty isn't stable, by the way: we haven't decided where is_empty should best live, though iirc we've basically agreed ExactSizeIterator is the wrong location.

if _ in container

I initially thought I wouldn't like this, but honestly, I don't dislike it.

Would the syntax move from the container? As a thought experiment, consider: if if value in optional worked to move from an option and conditionally process the value, would we have made Option: IntoIterator (thus for value in optional)? If it should be able to move from Option, how do we handle variable length collections?

I can’t easily check the std lib right now, but I believe every is_empty function takes &self. Other comparisons you’d typically encounter in an if statement also take reference parameters.

One arguable case to consider might be iterators. Say you call split() on something - does that deserve to have an is_empty function? It’s not really a container, but I can imagine someone thinking of the iterator being exhausted as empty. However, you can’t really tell whether an iterator is empty without changing its state, so this would not be compatible with a reference is_empty.

That being said, I haven’t heard anybody directly ask for is_empty for iterators (peekable or otherwise) and it seems like it would make more sense for containers.

So I was imagining that it would either take it always as & (allowing if mut x in y to take it as &mut or to do what the for x in y construction does and calls into_iter() on y.

FYI: Option<T> already supports for x in y and IntoIterator so I don't see why it wouldn't work for optional types.

As for variable length types (Vec, VecDeque, etc...) I think it would move/borrow the first n items if all are present (ie if x1, x2, x3 in y works only if y has at least 3 elements).

The last problem is what to do with &str, String, and others. Should it be done with the bytes or chars. The solution that is the most consistent with for is to do neither and let the user decide (ie if x in y.chars()).

I see that this syntax has been brought up before. However, there is a slight difference between these two proposals, that one was just for optional where as this one is for all iterators (or things that can satisfy T: IntoInterator.

I would like to see a trait like this in the standard library:

trait Container {
    fn is_empty(&self) -> bool;
    fn non_empty(&self) -> bool {
        !self.is_empty()
    }
}

With instances for all of the standard collection types (Vec, HashMap, etc.). This wouldn't require any changes to the language itself which I think is a big plus, it might just be added to the prelude.

2 Likes

After a long discussion, what should we do? Should we choose only one way to do this, or should it be outside the standard library and be a part of the language?

Personally I think Rust should do one of these two options:

  • Do nothing.
  • Just add a new function (not a new trait or language syntax). The name of the function doesn't matter much (Rust has made some naming decisions I've disagreed with, but you get used to them and life goes on, so the exact name isn't that important in my opinion as long as it's reasonable).

Adding a trait or language syntax is something that could be explored more, but adding a new (unstable) function to allow experimentation has a much lower barrier to entry (and I personally think we should stop giving Rust new syntax for a year or two) and it can always be deleted if it's deemed not worthwhile.

1 Like

I don't think that avoiding language expansion will save this situation; we must unify it at the syntax level, since this point was skipped during the language design.

I disagree. There are plenty of languages that don’t have syntax for this sort of thing. Those languages are just fine. Rust doesn’t need syntax for this. Rust’s syntax churn over the past few years has been exhausting. I, for one, need a break.

6 Likes

This does remind me of the ExactSizeIterator::is_empty method. That conundrum centers on the fact, that there are more things than just exact size-knowing iterators that know if they are empty or not.

A trait that provides both is_empty and non_empty could solve that problem, by moving is_empty off ExactSizeIterator and into a new trait.

With that said, I don't think a new method should be added. is_empty is good as it is, and reading if !predicate is something that becomes second nature. From the implementer side, we don't gain anything from having more redundant methods in the set of things you have to implement to be a typical collection type. I think we should focus on adding things to Rust that let us do more, not just something that becomes a new style lint in clippy and that's it.

3 Likes

FWIW, I added Option::contains and Result::contains a while ago.


In a language design side project, I added not to the Bool type and the results are promising so far – I'm even considering dropping unary operators (!, +, -, ~) altogether, in favor of methods.
Imho they have always been a relic of foregone times.

3 Likes

Could we implement not() method directly on bool type?

2 Likes