Language Tidy Up Feature 1 - unwrap Shorthand (!)

I'm submitting 2 language "tidy up" feature suggestions. The first is to use ! as syntactical sugar for "unwrap()".

The choice of operator is simple. It means "we're certain this will succeed" (exclamation point - slam it down) And fits well opposing the ? operator which naturally means "we're not certain" (ie, don't panic on this one). And it could also be "aligned with" the never operator in a way. "this never supposed to error - terminate if it does"

Though it has a different meaning on option, so maybe this would only be the case for unwrapping a result, not an option.

So

fn main() {
    let vec = Some([1, 2, 3]);
    println!("{:?}", vec.unwrap()[0]);
}

becomes

fn main() {
    let vec = Some([1, 2, 3]);
    println!("{:?}", vec![0]);
}

?

16 Likes

Well, now looking at it, and maybe you meant this, it kinda starts to take on macro syntax. So maybe ^ symbol. vec^[0] in your example

Rust's preferred manner of handling errors is to propagate them where possible, which is done with ?. Having to write out the longer .unwrap() is a good thing, in my opinion, as it shows a potential panic if you're wrong.

24 Likes

Might not be the most commonly used, but a ^ operator already exists :wink:

On the other hand, I’ll admit the the exact choice of symbol is not the most important point here.

4 Likes

good point

That's based on assumption that .unwrap() is somehow a flaw in the design that needs tidying up. That's not the case: .unwrap() was designed to be obnoxious on purpose, because you shouldn't use it in production code. Instead you should properly handle the errors via error propagation (?), explicit pattern matching, or error combinators.

9 Likes

I use it in production code. And I encourage others to do so as well. Full argument here: Using unwrap() in Rust is Okay - Andrew Gallant's Blog

EDIT: But I do not agree with adding a shorthand for unwrap(). It is good to have to write it (or expect()) out. While I'm fine writing unwrap()/expect(), I also wouldn't want to make it too easy or ergonomic to use. It is certainly something to use with caution.

19 Likes

(NOT A CONTRIBUTION)

Just as a historical note: this syntax was tossed around by the lang team prior to the 1.0 release in 2015. There was a consideration of changing the macro syntax (probably to @println instead of println!) to free up ! for this purpose. That wasn't done, and I don't think there's much appetite for this kind of change now.

7 Likes

Even so we have ! fixed for macros, it is still possible to use it for unwrap with a little more burden. The trick is that macros ! must be following an identifier, making it possible to enable writing (exp)! for exp.unwrap(), as well as variable.foo()! etc.

Even if there's a way in which it might be technically possible, I think the syntactic difficulties are a good indication that ! might have sufficiently many unrelated meanings in Rust already.

  • in case you count this at all it is part of !=,
  • and it's a prefix unary operator !expr for negated equality and negation, respectively;
  • it is used for macro invocations of the forms foo!(...), foo![...], foo! { ... } and macro_rules! foo { ... };
  • and last but not least, it's used in function signatures -> ! to indicate nontermination, and will eventually more generally be freely usable as its own type “!”, the “never type”.
13 Likes

I actually do, because I often ponder offering more negated things like that.

It probably will never pull its weight enough that I'd actually propose it, but my EE brain thinks of "NAND" as one thing, and thus would sometimes like to be able to just write a !& b instead of !(a & b).

6 Likes

Now that I'm thinking about it, why is this not part of the language already? Who wouldn't want it?

1 Like

People who like using !& for this :stuck_out_tongue:

use std::ops::Not;

struct Weird;

impl Not for &Weird {
    type Output = ();
    fn not(self) -> <Self as Not>::Output {}
}

fn main() {
    !& Weird;
}

(I couldn't find any actual ambiguous parses, but I do find this code highly amusing at least)

1 Like

Yeah, I'm pretty sure it's not any more ambiguous than the existing x && y vs x & &x vs &&z and such. The parser knows whether it's trying to parse a prefix unary operator or an infix binary operator.

1 Like

That would have the downside of being close to a &! b which already compiles but with a different meaning.

True, though I'd be willing to deal with that with formatting and linting. After all, foo() & &bar() and foo() && bar() also both compile, but mean different things.

Rustfmt would change a &! b to a & !b, which would help people catch it. And we could even lint for "hey, your whitespace is misleading here; please fix it".

(Like how while (x --> 0) in C is cute, but not something people should ever really be writing so linting it would be fine.)

But as I said, it's probably never going to be important enough to me to bother proposing it.

4 Likes

Well, rustfmt already does, and clippy has a lint as well (for unary ops in general). I do agree that if !& was added as a nand-operator, it would indeed be better for rustc to warn on a &! b.

With my optimization hat on I tend to think of and not as a single thing, since on x86 it has its own instruction (unlike nand), and I have actually written it with that spacing at times.

1 Like

The problem with NAND is that it's not associative. If you want to add a third condition the instinctive thing to write is a !& b !& c, but this isn't going to do what you expect...

4 Likes

a !& b && c = (a !& b) && c a !& b !& c // error

Also if we NAND maybe we should add NOR ( !| )