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.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.