Understanding Ok with error propagation operator?

Viewed 189

I've seen this pattern on more than one occasion:

fn f() -> Result<..., ...> {
   ...
   Ok(expression()?)
}

Specifically, I find the order Ok(expression()?) confusing. What's the return type of expression()? and is there an interplay between Ok and this type? It seems, the function Ok must do more than just capturing the value. But how can it force a return with an error, if it's the last expression wrapping the return type of expression()?.

2 Answers

The ? operator either returns (as in the return statement) the error variant, or extracts the value from the Ok variant. Thus Ok(expression()?) is more or less equal to:

// This pattern is so common in rust, that the language designers
// introduced the `?` to reduce the amount of boilerplate needed
let result = expression();
let r = match result{
    Err(e) => return Err(e.into()), // tries to convert the error into the required type if necessary (and if possible)
    Ok(r) => r
}

Ok(r)

So basically Ok(expression()?) can be simplified to just expression() if its Err variant matches the one from the function definition.

Historical context:

Extracting the Ok() variant and propagating the Err variant is a very common pattern in rust. In order to reduce the amount of boilerplate code, the rust team introduced the try!() macro; But it's so clumsy and does not really work well with method chaining. The rust team introduced the ? operator, which is doing absolutely the same thing, except for it's not so intrusive as try!()

? just leverages the error returned by expression if it exists or unwraps the value from within the returned Ok if not. Ok(expression()?) could be just expresion() and clippy would warn you about that:

warning: question mark operator is useless here
 --> src/lib.rs:8:5
  |
8 |     Ok(expression()?)
  |     ^^^^^^^^^^^^^^^^^ help: try: `expression()`
  |
  = note: `#[warn(clippy::needless_question_mark)]` on by default
  = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_question_mark

Playground

You can follow the documentation for further information

As per the comments. Notice that ? is useful if the error types differ but you have an implementation of From between error types or you use Box<dyn Error> as return type. In which case you can either, bubble up the error if it is an error, otherwise rewrap the value with an Ok

Ok(expression()?)

or, map your error type into the correct error type with map_err

expression.map_err(|e| ...)
Related