Search code examples
rustrust-cargo

Do I need to explicitly return the value in `Ok` variant of `Result` while using `?` operator?


In rust we know that ? operator works like match expression. If the Result is Ok then the result in Ok variant is returned from this expression and if the Result is Err then the result in Err variant is returned from this expression and quits the function immediately. So now in this code:

#![allow(unused)]
fn main() {

    use std::fs::File;
    use std::io::{self, Read};

    fn propagated_error() -> Result<String, io::Error> {

        let mut file = File::open("hello.txt")?;
        let mut data = String::new();
        file.read_to_string(&mut data)?;
        Ok(data)
    }

    let data = propagated_error();
    match data{
        Ok(data) => println!("{data}"),
        Err(err) => panic!("{err}")
    }

}

If I don't return the Ok(data) explicitly like that it was giving error:

error[E0308]: mismatched types
   --> src/main.rs:119:30
    |
119 |     fn propagated_error() -> Result<String, io::Error> {
    |        ----------------      ^^^^^^^^^^^^^^^^^^^^^^^^^ expected `Result<String, Error>`, found `()`
    |        |
    |        implicitly returns `()` as its body has no tail or `return` expression
    |
    = note:   expected enum `Result<String, std::io::Error>`
            found unit type `()`

Why???? As ? operator works, ? automatically returns the value in Ok variant, So why I had to explicitly return the value like this: Ok(data)???

Please help me to clear the idea.


Solution

  • As you said yourself, you have to explicitly return an Ok(T) variant. And the reason for that is simple. Result<T, E> is a different type than T. It has a semantic meaning of a result of some operation, but to the compiler it is just an enum. You could even design one yourself:

    enum MyResult<T, E> {
        Ok(T),
        Err(E),
    }
    

    So when you say that your function returns Result<T, E> you cannot just return T, because the types don't match.

    The question mark operator is a shortcut to propagate error variants. You could thing about is like it is a macro (in reality it uses an unstable Try trait, but this mechanism is not important to understand here), that expands following code:

    let x = foo()?;
    

    into

    let x = match foo() {
        Ok(x) => x,
        Err(e) => return Err(e.into()),
    };
    

    However it does not return data in Ok variant, as you stated before.

    To summarize. You can use ? to early return Err variant from function, but you need to explicitly wrap your "success" value in Ok variant, so that types match.