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.