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).
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.
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.
To
!is_empty()
, or tois_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 litterstd
with superfluous methods
And give Rustaceans cancer.
Nulliam Breakspeare
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 anot()
method even without the trait.
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.
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.
The most obvious example of which is imperative vs functional, such as loops vs iterators. Rust supports both.
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.
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?