Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is Rust's .expect() called expect? [closed]

Tags:

rust

Rust's .expect() is one of the most surprising names in the Result/Option space. While unwrap makes sense -- get a value from a Result by "unwrapping" it -- expect is surprisingly counterintuitive (to me).

Since so much of Rust is inspired by conventions from functional programming languages, I have been assuming that this is another example of "strange homage to obscurity," but when I asked the Duck it couldn't find an answer for me.

So, I give up. Why is .expect() the name for Rust's .unwrap_or_panic_with_this_message() function? Is this a reference to a feature in yet another functional language? Is it back-handed shade (or a complement) to Tcl? The result of too many late nights and too much Espresso at Mozilla?

What is the etymology of this plucky (but fierce!) little member of the standard library?

Edit:

Great news, everyone! After more than a year, we now have both an "explanation" in @GManNickG's tremendously well-researched and documented answer, below, and a "mental model" provided by @Peng's comment and link:

https://doc.rust-lang.org/std/error/index.html#common-message-styles

In short, it is now recommended that diagnostics for expect() should be written as "expect...should" or maybe "expect...should have". This makes more sense in the diagnostic output (see the link for examples) but also makes the source code scan better:

foo.expect("something bad happened")    // NO!

foo.expect("gollywumpus should be current") // YES!
like image 750
aghast Avatar asked Feb 25 '21 04:02

aghast


1 Answers

Summary:

No explicit reason for the name is given. However, it is incredibly likely the name comes from the world of parsers, where one "expects" to see a particular token (else the compilation fails).

Within rustc, the use of expect-like functions long predate use within Option. These are functions like expect(p, token::SEMI) to expect to parse a semicolon and expect_word(p, "let") to expect to parse the let keyword. If the expectation isn't met, compilation fails with an error message.

Eventually a utility function was added within the compiler that would expect not a specific token or string, but that a given Option contained a value (else, fail compilation with the given error message). Over time this was moved to the Option struct itself, where it remains today.

Personally, I don't find it unusual at all. It's just another verb you can do to the object, like unwrapping or taking or mapping its value. Expecting a value (else, fail) from your Option seems quite natural.


History:

The oldest commit of note is the following:

https://github.com/rust-lang/rust/commit/b06dc884e57644a0c7e9c5391af9e0392e5f49ac

Which adds this function within the compiler:

fn expect<T: copy>(sess: session, opt: option<T>, msg: fn() -> str) -> T {
    alt opt {
       some(t) { t }
       none { sess.bug(msg()); }
    }
}

As far as I can tell, this is the first function named "expect" that deals with inspecting an Option. Observe in particular this example use-case within the commit (which was implementing support for class methods!):

#debug("looking up %? : %?", def, class_doc);
let the_field = expect(tcx.sess,
    decoder::maybe_find_item(def.node, class_doc),
    {|| #fmt("get_field_type: in class %?, field ID %? not found",
             class_id, def)});

If the result of decoder::maybe_find_item is None, compilation will fail with the given error.

I encourage you to look at the parser code in this commit - there is extensive use of other expect-esque functions: e.g., expect(p, token::RPAREN) and expect_word(p, "let"). The name of this new function is almost obvious in this environment.

Eventually, the utility of this function was extracted and placed within Option itself:

https://github.com/rust-lang/rust/commit/e000d1db0ab047b8d2949de4ab221718905ce3b1

Which looked like:

pure fn expect<T: copy>(opt: option<T>, reason: str) -> T {
    #[doc = "
    Gets the value out of an option, printing a specified message on failure
    # Failure
    Fails if the value equals `none`
    "];
    alt opt { some(x) { x } none { fail reason; } }
}

It's worth noting that sometime later, there was eventually an (additional) function named unwrap_expect added in:

https://github.com/rust-lang/rust/commit/be3a71a1aa36173ce2cd521f811d8010029aa46f

pure fn unwrap_expect<T>(-opt: option<T>, reason: ~str) -> T {
    //! As unwrap, but with a specified failure message.
    if opt.is_none() { fail reason; }
    unwrap(opt)
}

Over time these were both subsumed by an Expect trait, which Option implemented:

https://github.com/rust-lang/rust/commit/0d8f5fa618da00653897be2050980c800389be82

/// Extension trait for the `Option` type to add an `expect` method

// FIXME(#14008) should this trait even exist?
pub trait Expect<T> {
    /// Unwraps an option, yielding the content of a `Some`
    ///
    /// # Failure
    ///
    /// Fails if the value is a `None` with a custom failure message provided by
    /// `msg`.
    fn expect<M: Any + Send>(self, m: M) -> T;
}

Spoiler for that TODO: that trait no longer exists. It was removed shortly after per:

https://github.com/rust-lang/rust/issues/14008.

More or less this is where we are at today.

I think the most likely conclusion is that the use of expect as a meaningful function name long predates its use in Option. Given it says what it does (expect a value or fail) there is little reason to break the pattern.

like image 163
GManNickG Avatar answered Nov 11 '22 10:11

GManNickG