Why can't the "?" operator cannot be used in workflow

I was trying to quickly drop some test results in a file for verification and ended up wanting to use the "?" operator in a scope. For instance:

struct MyErr {}

fn plop() -> Result<i32, MyErr> {
    Ok(42)
}
fn plap() -> Result<i32, MyErr> {
    Ok(43)
}

fn main() {
    let a: Result<i32, MyErr> = {
        Ok(plop()? + plap()?)
    };
    
    println!("{:?}", a);
}

which for me "should work" in the sense that my scope return a value, thus this value can be a result, the result type are compatible, etc.. but I get this error:

error[E0277]: the `?` operator can only be used in a function that returns `Result` or `Option` (or another type that implements `FromResidual`)
  --> src/main.rs:12:18
   |
10 | fn main() {
   | --------- this function should return `Result` or `Option` to accept `?`
11 |     let a: Result<i32, MyErr> = {
12 |         Ok(plop()? + plap()?)
   |                  ^ cannot use the `?` operator in a function that returns `()`
   |
help: consider adding return type
   |
10 ~ fn main() -> Result<(), Box<dyn std::error::Error>> {
11 |     let a: Result<i32, MyErr> = {
...
15 |     println!("{:?}", a);
16 +     Ok(())

where it seems that the "?" is catched by the main method instead of the scope. Is this normal or a bug ? Found it a bit confusing that the scope did not catch this operator.

The question mark operator wants to return from a function/closure, rather than a scope. What you're looking for is try blocks, but those are unstable.

6 Likes

this is excpected behavior, and a very large part of ? use relies on it working that way.

if you want you could write

let a: Result<i32, MyErr> = (|| {
        Ok(plop()? + plap()?)
    })();

which basiclly makes a new function to catch the ?.
or you could use and_then

let a: Result<i32, MyErr> = plop().and_then(|a| Ok(a + plap()?));

or a fun one

let a: Result<i32, MyErr> = [plop(),plap()].into_iter().sum();
2 Likes

This is exempt fro my book about Rust programming:

Another use of a closure to create a kind of a try block. For example,

let obtain_info = || -> io::Result<String> {
    let mut path = PathBuf::from(&dir);
    path.push(file);
    let file = std::fs::File::open(path)?;
    ...
    Ok(info)};
    
let Ok(info) = obtain_info() else {
    continue
};
...

So all your needs of using ? can be wrapped in a closure, until try blocks will be stable in Rust.

Thanks for all the answers, try blocks are indeed what I'm looking for. For the workarounds, I cannot say exactly why but creating and immediately call closures bother me ^^'. If findResult::and_then more elegant (at the cost of verbosity), I was actually searching for something like that but was searching for the "flatmap" keyword.

Ended up wrapping the filesystem logic into a separate function so that's good.