Nested ? operators where both sides are generic needs to be special cased

Consider this:

fn double<E>(dlg: impl FnOnce() -> Result<i32, E>) -> Result<i32, E> {
    let num = dlg()?;
    let num = num * 2;
    Ok(num)
}

#[derive(thiserror::Error, Debug)]
#[error("Number could not be generated")]
struct NumberGenerationError;

fn generate_number() -> Result<i32, NumberGenerationError> {
    Ok(4)
}

fn main() -> anyhow::Result<()> {
    println!("This works and generates {}.", generate_number()?);

    println!("This also works and genereates {}.", double(|| generate_number())?);

    println!("This works and genereates {}, but only because of he annotation.", double(|| {
        let num = generate_number()?;
        let num = num + 5;
        Ok::<_, NumberGenerationError>(num)
    })?);

    println!("This does not work and does not genereate {}.", double(|| {
        let num = generate_number()?;
        let num = num + 42;
        Ok(num)
    })?);

    Ok(())
}

(Playground Link)

It is clear why that last case fails to compile. generate_number()? is converting the NumberGenerationError to the implicit generic parameter E, and the ? on the entire double(...) call is converting E to anyhow::Error. So Rust knows that E: From<NumberGenerationError> + Into<anyhow::Error> - but that still doesn't tell the compiler what E is. It could be anything!

I think this should be special-cased in the compiler, to enable try_ versions of many higher order functions. I see two ways to solve this:

  • Inside-out: the type of the error generate_number()? needs to convert to cannot be inferred, but we know it needs to be convertible from NumberGenerationError, so just use NumberGenerationError.
  • Outside-in: the type of error in double(...) cannot be inferred, but we know we need to convert it to anyhow::Error, so just use anyhow::Error.

I think the outside-in approach is better, because it'll work when there are multiple ?s of different types inside the closure.