Re-opening deprecating Option::unwrap and Result::unwrap

I wanted assert_* to take a message because in my code the reason why an unwrap can't fail is usually much less obvious.

These strike me as only truly fixable by better const evaluation that can avoid the need to write an explicit "and then fail compilation if I typoed the regex or the arithmetic" annotation.

A survey would be illuminating. I expect it would be a majority of library crates where it would generate lots of false positives.

This thread is tempting me to write a small library crate that does nothing but define a ResultExt and OptionExt with a set of “self-documenting unwrap alternatives”:

pub trait ResultExt<T, E> {
    /// Use this like [`unreachable!`]:
    /// you believe that the error case cannot occur.
    fn err_is_unreachable(self) -> T;

    /// Use this like [`todo!`]:
    /// the code is incomplete and error handling should be added.
    fn err_is_todo(self) -> T;

    ...
}

impl<T, E> ResultExt for Result<T, E> {...}

With such a library, people who really want precise naming of the different kinds of unwrap usages (and value this over sticking to existing convention) could have it, and even without using it “in production”, we could apply it to existing crates to get some additional evidence of the value and cost of adding such a feature to the standard library.

14 Likes

I write embedded code with harsh constrains. In situations like this we try to do what we can and log. In this case, we would have taken only 200 features instead of what is given, truncated basically. Then logged or triggered manual inspection of some sort. This still would have lowered the quality, but not as in original (non-rust) code. I'm telling this because just returning an error is not error handling, it is just user space unwinding.

3 Likes

Fair enough, but I thought it was related to this:

I get the impression the FL was spreading erroneous configs around.

Speaking of that this part caught my glance.

Is this another use case for linear types?

Yeah, I'm hoping at some point const and future comptime support essentially all of Rust.

3 Likes

Cloudflare’s incident had nothing to do with syntax; the real issue was that they assumed that part of the system would never fail. Switching to another language wouldn’t have helped.

If Rust wants to improve, it should raise the cost of using unwrap, make it more of a hassle to write, and push developers to use it less.

Renaming unwrap is pointless, because everyone already knows it panics, and even with a new name it would still be convenient to write.

Either unwrap and expect should be fully deprecated, and if a programmer wants to panic, they should add their own if check and then call panic!().

Or Rust could do what unsafe{} does and add a color-coded panic{} block. Anything that might panic—like unwrap, panic!(), or a[a.len()]—would have to be wrapped inside panic{}.

4 Likes

Actually why is even this a discussion? I mean it wasn't the languages fault this happened, right? So nothing should actually change, because the code did exactly what it was told to do. The problem lies somewhere else in the logic, not in the language.Is it really needed to make a change here with unwrap?

1 Like

“People should just write code that means what they actually want, and there won’t be a problem” empirically doesn’t work. Good language and library design helps guide people to write the right thing before it fails in production. It’s possible — likely, even — that Rust could not have done anything that helped today's particular case, but as a general rule, we do not accept the “you’re holding it wrong” argument. If people hold it wrong, build better handles.

23 Likes

I really don't see the value in this. Having people write .unwrap_or_else(|_| panic!()) instead of .unwrap() doesn't fix things, just gets them annoyed at the language.

(Same way I think it's good that you can write slice[i] instead of needing to write *slice.get(i).unwrap().)

14 Likes

I don't think the naming really masks the underlying behavior once it has been taught

Every Rust programmer knows it can panic, so that shouldn't be the issue

Everyone already knows it panics

The issue isn’t that people don’t know .unwrap() can panic — it’s that its name and ergonomics fail to clearly convey the risk. I understood from the start that .unwrap() could panic, but it took experience and time to appreciate how easily it slips into production code.

Even now, I sometimes use .unwrap() as a temporary placeholder, telling myself I’ll handle it later... someday.

I should also note that any potential confusion caused by a change will only grow the longer we wait, and the resulting churn should not carry significant weight in this decision — though I suspect it inevitably will.

Still, I feel encouraged that many in this (and other) discussions agree the current naming and/or usage of .unwrap() are not perfect and could be improved. Although I came here strongly favoring a .unwrap()'s rename (.unwrap_or_panic() does seem to reflect its purpose so much better), I now see it’s a complex issue with many reasonable alternatives — and naming is only part of the problem. :thinking:

2 Likes

I find Rust's ownership, borrowing rules, and lifetime annotations annoying, but that annoyance is worth the safety. Since panic similarly affects system safety, it should also be restricted in a way that makes it annoying to use.

1 Like

I like more specificity in why the "unwrap" is there, but I think there is something missing here. Some have mentioned the desire to fail loudly but without stopping the entire application, so it might be good to have variant that make as much noise as possible without crashing (possibly allowing the developer to overwrite how/where to log/send that) and fall back to a default value. It's already possible to do that, but requires more boilerplate (e.g. let else):

  • Bikesheddable: .expect_fallback(custom message", value_to_use_if_it_failed)

Currently we have unwrap_or and unwrap_or_default, but they fail completely silently, which is sometimes not what you want.

I'm not sure how much that'd actually be used in practice, but I suspect it's more likely to be used than a multi-line error handling and there are some situations where failing with a panic is way worse than continuing with not-quite-correct data (hence why unwrap_or exists), but if you want it to make a noise you currently need something like this:

foo.map_err(|_| println!(...)).unwrap_or(0)

Though arguably that wouldn't be an unwrap/expect since it doesn't actually panic.

1 Like

What is comptime? A const fn that returns code? Future macro system?

I think unwrap is indeed not a straightforward name. When I was a newbie at Rust, I thought to get the value I use .unwrap() because the tutorial I followed was also using unwrap, so this is indeed a footgun. I only understood it would panic after I once got a panic crash from unwrap, then I started to search deeper about unwrap and realized that it would panic if there is an error

But if it is named or_panic(), like

insert_data(data).or_panic()

The name is straightforward, just from reading the syntax, the user could already understand that it would insert a data or panic. Easy beginner experience, easy code screening, lower cognitive load

Expect is also not a straightforward name. When I was a beginner at Rust, I was also confused about expect, I thought the code would expect this value, but it turns out that it is for panicking the app with a custom message

It is easier like this:

get_data().or_panic()
get_data().or_panic_with("no data available")

The user instantly understands it would get the data or panic, and get the data or panic with this message

The tutorial would follow, we just need to update the official tutorial, the 3rd party tutorial would adapt, just like when Rust does breaking changes between editions

After the user reads the name syntax .or_panic() and .or_panic_with(), he then faster understands they would do the intended operation or panic

Could it be automated with a tool like a find-and-replace button in a code editor?

I have written a transpiler that replaces Javascript syntax to Rust syntax, so we could just write an automation tool that would replace the unwrap word to or_panic, and expect to or_panic_with

6 Likes

comptime is AFAIK a name borrowed d from Zig, roughly meaning, anything that can be computed... well at compile time.

Ie if compiler could prove (at compile time, comptime) that a certain function won't fail in this case, one could potentially omit the .unwrap() completely. Such code would fail to compile would the compiler be unable to prove that the function won't fail

1 Like

Automatic conversion of source code is indeed mostly possible with cargo fix --edition

Main issue is sociological, a lot of documentation, blog posts, older codebases, tutorials,... already use unwrap

1 Like

const can run at both compile time and runtime. comptime can only run at compile time. And yes, one use for it is as part of a future macro system.

8 Likes

If we do want to make the developers more cautious about unwrapping, a definite solution is to invent something similar to unsafe keyword, for example, may_panic, which means the it's the developers' responsibility to make sure the code does not panic:

pub may_panic fn foo(a: Option<Config>) {
    let config = may_panic { a.unwrap() };
}

Another point of mine is what I said in this post, which was about the Rust-related Windows kernel bug (another unwrap-caused bug), that we should make people aware that WHAT vulnerability would safe Rust have. The unreal illusion that Rust will prevent all vulnerabilities is the key to the recent criticism or sarcasm to Rust I read in the Internet.

1 Like

The problem with this line of thinking is that many years from now the migration process will only get more costly and painful. And then we are ethernally stuck waiting for a worthy amount of papercut fixes.

Thats an extremely demoralizing thought, that we are bound to become C++2, a bloat of papercuts never fixed for the fear of annoying some third party.

6 Likes