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(())
}
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 fromNumberGenerationError, so just useNumberGenerationError. - Outside-in: the type of error in
double(...)cannot be inferred, but we know we need to convert it toanyhow::Error, so just useanyhow::Error.
I think the outside-in approach is better, because it'll work when there are multiple ?s of different types inside the closure.