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

At the risk of indulging in and exemplar of bikeshedding:

How about changing style so that !condition becomes ! condition. This would increase the visibility of the !

Agreed on both aspects: requiring whitespace around the ! sigil, and partitioning long method chains into line-separated semantically-meaningful subchains. The above example was clearly just that, a demonstration of how easy it is to lose a juxtaposed leading ! sigil in a long r-value expression.

If it wasn't oddly close to macro syntax, it'd be nice to have infix-like ! operator:

if long.chain.!is_empty()
4 Likes

Parsing of an infix-like ! operator shouldn't be ambiguous. The relevant syntax alternatives seem to be

ExprKind =
  | Unary:{ op:UnaryOp expr:Expr }
  | Field:{ base:Expr "." field:FieldName }
  | MethodCall:{ receiver:Expr "." method:PathSegment "(" args:Expr* % "," ","? ")" }
  | …

This change would require accepting (a perhaps limited subset of) op:UnaryOp before both field:FieldName and method:PathSegment. Note that for MethodCall there may still be wide separation between the prefix UnaryOp and the end of the expression where the UnaryOp is finally applied. To me that implies that the problem of correlating the operator with it's action is caused by the prefix position of the UnaryOp; a postfix UnaryOp would solve the problem by placing the operator in the visual location that corresponds to when it is actually applied.

In summary, I don't think an infix-like ! operator would help; the real need is for postfix unary operators. Postfix macros (e.g., .not!() ) would also be a reasonable way to address this problem of having an action operator visually separated from its point of application.

3 Likes

I'd want to paint that bikeshed is_non_null() though, matching the NonNull type.

What would it take to have the compiler automatically infer "is_not_foo" or "not_foo" or "not_is_foo" for all methods that are of the form "is_foo" or even "foo" that return a boolean value? In other words, if a trait or impl is defined that is of the form "is_foo ( ... ) -> bool" then, if the compiler encounters a function or method call like "is_not_foo( ... ) -> bool" it automagically treats it as "!is_foo( ...)" or it automagically auto-monomorphizes "is_not_foo( ... ) -> bool { !foo( .. ) }" (whichever seems like the best strategy from a soundness perspective).

Still, I really like the, bar.is_foo( ... ).not() that is already supported by the std lib, but, of all the proposals, it seems like inferring a logical negation for all methods/functions that return boolean would be the most ergonomic thing, so you'd have:

fn foo ( ... ) -> bool implies fn not_foo( ... ) -> bool exists if not already defined

fn not_foo ( ... ) -> bool implies fn is_foo( ... ) -> bool

fn is_foo ( ... ) -> bool implies fn not_foo( ... ) -> bool

fn has_foo ( ... ) -> bool implies fn not_has_foo( ... ) -> (similar, for opposite)

etc...

In other words, there is a defined mapping of boolean function name patterns to their logical negation and declaring/defining a function/method of one of those patterns causes the compiler to automatically generate the logical negation according to the proscribed pattern (unless opted-out at the definition site with an attribute).

It seems like this is something that could automatically be done for the next language edition in a backwards compatible fashion. For the current edition it cold be opt-in at the definition site.

In the current edition, if not opted-in, it would not automatically generate the logical negation. On the next edition, if not opted-out, it would automatically generate the negation (unless explicitly opted-out).

Why are you saying that !v.is_empty() has double negation? Are you considering "empty" to be a negation? If so, then isn't is_nonempty also a double-negation?

2 Likes

semi-serious suggestion:

if v.pybool() {
    ....
}

pybool could be implemented for lots of types (Option, Vector, HashMap, Result, ...) and it would behave like python's "truthiness" (__bool__ in python3).

I wonder if it would be possible to make pybool support v as bool :thinking:

edit: I like is_truthy() and is_falsey better though. Someone should make a crate called truthy and do this. Once everyone is extremely truthy we can make rust more truthy via RFC.

I just had my old C++ PTSD triggered, caused by a colleague's if (!p->is_not_unused()) ... @_@

While I'm not terribly bothered by is_empty(), in general I find it easier to correlate truthiness with "has elements". Python for the most part does a good job in that regard.

Not a realistic suggestion at this point in time, but using !! for negation might have been easier to read, and be symmetric with the other boolean operators using double sigils.

7 Likes

Often I see C programmers (especially with embedded) create Macros for most, if not all, of the Logical/Bit-Wise operations so that they stand out more. This seems to indicate that the use of things like "!" to mean "not" was misguided always and should've never have happened in the C-like languages. Pascal and family got this right. C and family got it wrong. Unfortunately, most modern languages have settled on mostly C-like conventions on this for no reason other than inertial. Perhaps what is needed is a new set of keywords for operators and a lint that recommends to use the keyword instead of operator in most cases.

5 Likes

Clippy is getting this improved!

3 Likes

All of !vec.is_empty(), vec.is_not_empty(), and vec.is_nonempty() have exactly the same message "contains elements because it's not without elements" which has double negation "not without", therefore all of them in this sense are equivalently hard to understand.

Instead a more concise way to say "contains elements" could be vec.collected() which additionally has a lot of meaning alongside with iter.collect().

However, there's a drawback that vec.collected() is very similar to iter.collect(), so because of that it may be better to have vec.any_collected() or something like that.

let words: Vec<_> = text.words().collect();
if words.collected() {
    ...
}

vs

let words: Vec<_> = text.words().collect();
if words.any_collected() {
    ...
}

I get that you see "has elements" as positive and "has no elements" as negative.

I don't think the English lines up with your intuition here. "Empty" is a singular concept, explained as "has nothing", but the single words for the concepts are "empty" and a "nonempty".

By this, "is not empty" is a single negation, because it parses as (not (the state of having no contents)), not (not (invert (the state of having contents)). That would instead be "is not nonempty".

If I had to state "has elements" as a positive in today's Rust, I'd do .iter().any(|_| true).

3 Likes

I considered "empty" to be a negation in writing that, but I can also see it as just a singular state, as I do with nonempty.

IMHO the reason something like has_elements() is preferable over is_empty() is not so much because of a notion of negation, but because it's more commonly useful:

if thing.has_elements() { thing.do_something_with_elements(); }
while thing.has_elements() { thing.pop_element().do_something(); }

Python's notion of truthiness makes these particularly elegant, IMHO:

while customers:
    yield customers.pop().get_name() or "Derek"
8 Likes

Would it be possible to move fn len(&self) -> usize into a trait and then provide is_empty and has_elements has provided methods?

Note that the second and third examples here are un-idiomatic in Rust, where they would be

while let Some(top) = thing.pop() { top.do_something(); }

and

while let Some(top) = thing.pop() { yield top.get_name().unwrap_or("Derek"); }
7 Likes

This seems like it moves towards having one more thing you need to memorize in order to write Rust code, which I don’t think is a good thing.

If the clippy lint is the main problem because people copy the first quoted thing instead of the actual suggestion, maybe the order should be swapped.

Otherwise it seems like this would be easy to experiment with via some traits in a third-party crate on the standard types.

3 Likes

I don't have no problems with not unusing operators not unlike not !is_not_unused().

Or not?

19 Likes