rust-lang / rust

Empowering everyone to build reliable and efficient software.
https://www.rust-lang.org
Other
99.03k stars 12.79k forks source link

Type inference for the question mark operator #123793

Open mateiandrei94 opened 7 months ago

mateiandrei94 commented 7 months ago

I believe that in certain scenarios the question mark operator should infer the err variant of a result type

I tried this code:

pub fn inference<T, E, F>(mut f: F) -> Result<T, E> 
where F : FnMut () -> Result<T, E>,
      E : std::error::Error,
{
    f()
}

#[test]
fn inference_tester() {
    inference_user();
}

fn inference_user() {

    use std::env::VarError;

    // in this example x should always be inferred to be of type Result<String, VarError>

    // works fine, nothing ambiguous
    let x : Result<String, VarError> = inference(|| {
        Ok(std::env::var("key")?)
    });

    // works fine, it's ok to infer T to be String
    let x = inference::<_, VarError, _>(|| {
        Ok(std::env::var("key")?)
    });

    // works fine, apparently it's ok to infer the Ok variant
    let x : Result<_, VarError> = inference(|| {
        Ok(std::env::var("key")?)
    });

    // type annotations needed, apparently it's NOT ok to infer the Err variant
    let x : Result<String, _> = inference(|| {
        Ok(std::env::var("key")?)
    });

    // works fine
    let x = inference(|| {
        let result = std::env::var("key");
        // attempt to do what let result = result?; would do
        let result = match result {
            Ok(ok) => ok,
            Err(err) => {
                // the compiler "knows" that err : VarError
                return Err(err); // the ? operator uses From::from(err)
            },
        };
        Ok(result)
    });

    // works fine, apparently it's ok to infer the Err variant from dead code
    let x = inference(|| {
        let result = std::env::var("key");
        let result = match result {
            Ok(ok) => ok,
            Err(err) => {
                return Err(From::from(err));
                return Err(VarError::from(err)); // unreachable statement
            },
        };
        Ok(result)
    });

    // works fine, apparently it's ok to infer the Err variant from dead code
    let x = inference(|| {
        return Ok(std::env::var("key")?);
        Err(VarError::NotPresent)
    });

    // type annotations needed, but are they really ?
    let x = inference(|| {
        return Ok(std::env::var("key")?);
    });

}

I expected to see this happen: everything to compile, because all the types are actually known, I would've understood a type annotations needed compile error if I had multiple Err return points with different error types in which case inference would not have been possible.

Instead, this happened: compile error type annotations needed

Meta

rustc --version --verbose:

rustc 1.77.1 (7cf61ebde 2024-03-27)
binary: rustc
commit-hash: 7cf61ebde7b22796c69757901dd346d0fe70bd97
commit-date: 2024-03-27
host: x86_64-pc-windows-msvc
release: 1.77.1
LLVM version: 17.0.6
eggyal commented 7 months ago

As documented in The Rust Reference chapter titled The question mark operator:

When applied to values of the Result<T, E> type, it propagates errors. If the value is Err(e), then it will return Err(From::from(e)) from the enclosing function or closure.

This use of the From trait is really useful, as it enables the type of error on which ? is applied to be anything that can convert into the error that the enclosing function returns. However, if the compiler does not know what type that is, you will necessarily have an inference error.

mateiandrei94 commented 7 months ago

@eggyal Thank you, yes, I understand and am aware that ? uses From::from(err), I also mentioned it a comment in the example (see below), however as stated in the first sentence in my bug report: I believe that in certain scenarios the question mark operator should infer the err variant of a result type. By that I don't mean change the behavior of the ? operator, but rather that inference::<_, VarError, _> should be automatically added silently, in which case the ? would work fine using From::from. As stated in the form of comments in the code example, the compiler already infers T in Result<T, E> I'm simply saying that when E is not known it should be inferred in the same way that T is.

I'll update here my expectations: 1) in the case the error variant in 100% known, it cannot be anything else inference::<_, VarError, _> should be added. 2) in the case where we use ? but it doesn't always return the same E type annotations should be needed

    // works fine
    let x = inference(|| {
        let result = std::env::var("key");
        // attempt to do what let result = result?; would do
        let result = match result {
            Ok(ok) => ok,
            Err(err) => {
                // the compiler "knows" that err : VarError
                return Err(err); // the ? operator uses From::from(err) <<<<<<<<<< Here
            },
        };
        Ok(result)
    });