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

end of thread

8 Likes

Is this any niche functionality, or is the current syntax perfect? What is exact reason of not having this sigil?

Note that even if it looks similar, this is not the same as JavaScript truthiness:

  • In JS everything could be true/false, but this allows only appropriate values to be true/false
  • In JS truthiness is implicit, but this requires explicit operator for it
  • In JS truthiness is built-in, but this utilizes type system to define how it works

Is it affected with any problems from JavaScript truthiness?

I find this question is backwards, we should never ask why not add a sigil, instead we should ask why should we add a sigil. In this case, there is insufficient motivation to add a new sigil into Rust.

This would allow anything that implements a trait to by truthy or falsy.

I'm not sure how that's significantly better. I don't like truthy/falsy values in general, so I really care if they are explicit or implicit.

The type system is turing complete, meaning that it can be just as complex as a built in operator.

Truthy/falsy values tend to lead to cryptic code, especially when used on more complex data types. But because they are so easy to use, people will try and shove them onto any type that fits (even if it doesn't have an obvious truthy/falsy value). So just by introducing them, truthy/falsy values tend to make code harder to read because they are so easy to use. (disclaimer: this is entirely my opinion, not based on very many hard facts, although it does seem to be a common sentiment).

3 Likes

Technically, we can already coerce anything to bool. The standard explicit coerce-to-boolean in JS/TS is !!val. As you can implement ops::Not on any type, you could make this idiom work on any Rust type. (Well, modulo ownership issues.) (+val is the idiom for coerce-to-number.)

That said, I agree that we don't need a unary plus operator, and it probably shouldn't be a boolean coersion.

2 Likes

Sigil replaces current syntax, which is complex, verbose, and also error-prone. It generalizes the same concept which applies for many similar types. It's also suitable to represent the underlying idea.

This still not makes it the same as JavaScript truthiness. And the fact that operator could be implemented for anything doesn't mean that it would be implemented for anything.

I don't propose to add truthy/falsy values but rather I propose operator that allows to identify when specific values have any positive charge. Truthiness is more general and undefined concept.

It's possible to abuse implementations of all operators in Rust (including this), but it doesn't mean that they would be abused on practice. There's no reason for prefix + implementations to become complex.

Not so easy, since implementation of operator has its own cost, and if someone want to provide it then he should really have a good reason for that. Always providing bool returning methods in Rust is easier than implementing operators, and in cases you described using them is easier as well.

Even if it's possible to coerce anything to bool with !! that's absolutely not practical solution. Just imagine counting of ! symbols to understand what we expect true or false and then imagine guessing if anything has bool type or some different. Also, it not even resembles that we check for positive charge when applying it, so here we have real truthiness from JavaScript with all of its weirdness.

Coercing prefix + to numbers also not seems to be practical solution, even if it's somehow expected to work like that. What value it should return, length of containers or inverted negative numbers? The first wouldn't be used so often plus in most of cases it anyway would be used to check for non-zero length, and the second also wouldn't be used so often plus we already have prefix - which does the same task.

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

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