Why can't I use the `?` operator in this way?

Ah, thanks. That might be a part of the puzzle I'm missing.

In general I have a downer on the "happy path" programming style that tries to keep all the clutter of error handling out of the purity of the "happy path" by sweeping errors under the carpet with exceptions or whatever.

In my world things fail all the time and you had better be thinking about it. The "happy path" is the exception :slight_smile: Better to have all that error checking in your face when you read the code. Rather than having to fathom what on Earth happens when control flow is suddenly hyper spaced to God knows where by an exception.

3 Likes

Or by the and_then combinator. If you need your functions to only be locally short-circuited, then a method works perfectly fine, with closures that are only run in the happy case, etc. It might be prettier with a try block and ?-s, but it works also without.

What doesn't work with a method is the "global short-circuiting" of early-return. This needs special support which lets you return out of the surrounding function (or a macro, like try! was before ? was introduced).

So your example can be written as:

fn foo() {
    let some_value_i_dont_care_about_i_only_want_the_side_effect = {
        some_opt_func1().and_then(|a|
        some_opt_func2(a)).and_then(|b|
        some_opt_func3(b)).and_then(|c|
        do_something_with(c))
    }
    printfn!("the side effect was done only if a chain of options were all Some")
}

But this example:

fn foo() {
    let some_value_i_do_care_about: MyNonOptionType = {
        let a = some_opt_func1()?;
        let b = some_opt_func2(a)?;
        let c = some_opt_func3(b)?;
        do_something_with(c)
    }
    println!("we get here only if a chain of options were all some");
    do_something_with(some_value_i_do_care_about)
}

cannot be rewritten with methods. Hence, ? exiting out of the function by default (which requires the function to have an appropriate return type).

And this is why you need to use ? in Rust to bubble the errors up, instead of them explicitly poping up like exceptions. With ? you are acknowledging that this function might fail, and expressing your unwillingness to handle the failure inside this function, delegating this to the caller. However, for this to work, you need to explicitly explain how to report this kind of error to your consumer, via a From impl converting the error-type returned from the inner function to the one returned by the outer function. In many cases, this includes wrapping the original error, so your consumer can still inspect it:

enum MyError {
    GotSystemError(SystemError),
    GotNumberError(NumberError),
    ...
}

impl From<SystemError> for MyError {
    fn from(se: SystemError) -> Self {
        MyError::GotSystemError(se)
    }

...

As mentioned above, there are crates made to reduce some of this boilerplate, since you prefer to know exactly what error happened, I would recommend using thiserror.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.