Unwrap operator

In TypeScript and inexistent EcmaScript 4th edition, the expression o! is a nullish-unwrap. It's similiar to o.unwrap();.

Unfortunately Rust already uses postfix ! for macros. I'm wondering if it's possible to support something similiar? Maybe double !, like:

let _v = o!!.f();

Or:

let _v = o?!.f();

(Note the punctuator ?!.)

This saves 6 characters (compared to o.unwrap().f();), but I'm not sure it's clear to everyone it means "unwrap".

We have ? which unwraps but returns.

Since the vast majority of the time you don't want your program to panic, you'd only use this new operator for "quick and dirty" programming or in situations where you know unwrap should always be successful.

Is that a strong enough reason to adopt a new operator?

11 Likes

That makes sense. I didn't think of ? because I often associate it with EcmaScript's o?.x, which works different from Rust's o?.x; But there are few times you want to unwrap and panic on None or Err.

I don't agree though on using percentage like this. It influences others to do the same without researching. Just say "rare" instead of percent?

Yeah there a few times but then we can just use unwrap (or better expect)

1 Like

Assuming that's "null if it was null, or x otherwise", that'll be try { o?.x } in rust soon™.

4 Likes

Unwrap is generally discouraged (in favor of ? or failing that expect).

I don't think it makes sense to give it a first-class syntax which would encourage it.

FWIW, these days I tend to add a deny(clippy::unwrap_used) attribute to my codebases to lint against using unwrap.

10 Likes

Hmm. Maybe we can have even better solution. We don't just have an o, we always work with some method that have unwrap() (e.g. Result, Option)

let we have function (or method) with such return type:

fn try_do(...) -> Result<T, E>;

Then you want to shrink this expression:

/*no matter function or method, we starts from exact function */
// You have
try_do(...).unwrap().f()

// You want
try_do(...)?!.f()

So, since we have ? op, we can return not a Result<T, E> but PanicResult<T, E> (or just PanicResult<T> if it never suppose to get safe Result<T, E>:

fn try_do(...) -> PanicResult<T>;

// Previous
try_do(...).unwrap().f()

// Becomes
try_do(...)?.f()

WITHOUT any changes in Rust!

But I totally agree with @bascule. We need to use unwrap since it agreement that says I know what I'm doing

1 Like

I was just thinking about a feature like this, and I think the most interesting angle on it is from the perspective of an API designer. I can imagine a lot of cases like this where a maximally-convenient unwrapping operator could push an API to return Result instead of panicking internally. Rust APIs often have this slightly awkward tradeoff like:,"Should we return a Result or just panic on errors? Or should we maybe implement both versions?" Standard library functions like .split_at() looked at that tradeoff and concluded that almost all callers were going to unwrap the result anyway, and I think that was a reasonable choice. But the end result is that even in "slow and clean" programs, every time you call .split_at(), you're burying a potential panic.

What if split_at returned a Result? Well, I'd have to write .split_at().unwrap() all over the place, and I'd hate it. But then what if, instead of that, I could write .split_at()! or something similarly short? I don't think I mind typing that. In fact, I like typing that, because I like seeing that a panic can happen there. So I'm interested in this feature, not because I want my programs to panic more, but because I want my libraries to let me panic instead of panicking for me :slight_smile:

5 Likes

That puts a lot of burden on the library API designer, as well as on the users who want to avoid needless panics. Currently it is the case that violating function's preconditions would likely lead to a panic. If we went your way, then a library shouldn't panic in its functions at all. But that means that they would need to design a separate, sufficiently rich error variant for every possible API misuse! Everything which is now covered with a panic message, with a nicely formatted error string and an automatic precise backtrace, would need to be turned into some error variant. There would be a lot of such error variants propagated upwards!

Worse, if a function nowadays returns Result but also panics, then what should it return in your non-panicking world? Should it be Result<Result<_, _>, _>? That would just increase confusion which of those results is expected to be unwrapped. Or maybe we should flatten the errors into a single Result? This would make it impossible to easily unwrap only the API-misuse errors, while dealing with proper fallibility

It would also mean that the library can't add some new assertions inside the function bodies, because that would change their APIs. I don't thinkt choosing between better correctness and worse ergonomics is something to encourage.

Finally, the unwrapping wouldn't even be used consistently. Either everyone just propagates those "pseudo-panics" upwards, in which case you have lost the powerful panic mechanism and just made the usual Result-based errors worse, or people still unwrap errors which are expected to never happen. Realistically, the first option won't happen at the ecosystem scale, so you'd be forced to deal with panics in dependencies anyway, so what's the benefit in the first place?

Note that you can always use catch_unwind to call a panicking function while capturing all panics into a Result. It's more verbose, but I also don't think there is any good reason to do it, outside of toplevel error handlers.

5 Likes

Yeah, split_at is annoying. It's currently a contentious design issue:

If I had to redesign the language from scratch, I would tell the opposite : a trivial looking method like unwarp() seems pretty inoffensive, while the exclamation mark is usually used to express danger. To me, the exclamation point seem much more appropriate for potentials panics than for negation, macros or never returning functions. Using ! only to complement the ? would be neat and consistent.

The problem is that the exclamation mark is already used for three different purpose. It's far too late to add a forth one that would not combine nicely with the existing ones. I believe this opportunity has gone long ago .

This can be ameliorated via linting or editor highlighting; it's not the case that it needs to be a symbol. ! is a bit small and confusable anyway, perhaps would be received better.

1 Like

Unfortunately there are problems with using emoji for programming:

  • input: keyboards lack keys for this, so typing it once will be a heavy-handed affair relative to pressing shift+1.
  • legibility: while most IDEs and websites support properly displaying unicode, not all terminals support unicode fully. This opens up a can of worms.

I acquired these opinions partially after authoring a code base that uses Greek letters to keep the distance to the papers as small as possible. From that perspective it is a great success, but input was the main thing that I just had to deal with, in that it required copying the relevant characters from my local char map application whenever I needed them rather than just being able to type them.

So while it was worth it in that project, it isn't an experience I'd want working on every code base.

Somewhat off topic, but I have had exactly the opposite experience. Compose key sequences (os dependent) allow for easily mapped input. <right-opt> g a types α for me, <right-opt> g b types β, and so on.

For a programming language preprocessors/plugins can be used. If cargo fmt (and the IDE or anything else) knew to replace \delta with δ then it becomes very easy to type, Lean is an example of a programming(ish) language which does this with math symbols.

I don't think rust should adopt for unwrap by any means, I just don't think it's as insane to use such symbols as it's often painted.

4 Likes

Yeah, emoji are easier to type than {, on lots of input devices.

(With built-in windows features on my normal US keyboard it's easier for me to type :crab: than €, for example.)

As you said, that doesn't mean we should use them, but if there's something where they fit well it doesn't seem impossible. More likely than ‽, for example. Even though I have a compose key setup so that's easy.

Thing of those solutions is, they're custom, possibly even requiring a custom keyboard (what is a compose key exactly? Some sort of macro key?)

AFAIK I can't just grab a random computer and expect that to work without manually configuring that first. And that is important for much the same reason that we don't rely on IDE features for code legibility. If an IDE or editor enhances it then that is a great thing, but code should be readable even when viewed in the most basic editor ever eg notepad. Similarly code should be typable regardless of the hardware setup or software config, and sadly that means requiring the lowest common denominator.

It's something that the Unix world tended to have, while macOS and Windows didn't - Compose key - Wikipedia

The core idea is that rather than using Alt or Option with a key to get an alternate character, you can type Compose then a sequence of characters that's visually similar to get what you want. For example, to type ç with a Compose key, you'd press Compose, then c, then ,, because ç looks like the combination of c with , in the same character cell. Similarly, é is Compose, ', e or Compose e ', again because é looks like the combination of ' and e.

The alternative to both AltGr/Alt/Option and Compose used on some keyboards is at least one Dead key - Wikipedia - you have a key for the accent character, and to type é, you'd press the dead key for the accent, then e.

4 Likes

This could be a viable option, especially if it's done by the author (eg using a run-rewriter-on-save strategy) rather than the reader, and such that the rewritten version can just be checked in. Even then I'd still want the easily-typable version to be a valid language-level synonym for the nice glyph.

To take the delta example, if \delta is rewritten to δ, then I'd want tooling to accept both versions as valid, even though the intent is for code to end up in the δ form.

The point I was making is that typing emojis now is the lowest common denominator.

(Things like × and ≤ still aren't, but Win+. on Windows or Control+Command+Space on Mac are built-in.)

1 Like

Is it? I'm using NixOS and typing an emoji is about as easy as typing an alpha. Which is to say, currently I need a charmap application for that.