Closure type inference vs. `?`

Consider the following code:

fn foo<E, F: FnMut() -> Result<bool, E>>(f: F) -> Result<bool, E> {
    todo!();
}

fn qux() -> Result<(), &'static str> {
    Err("qux")
}

fn bar() {
    foo(|| {
        qux()?;
        Ok(true)
    });
}

This fails to compile with:

error[E0282]: type annotations needed
  --> src/lib.rs:10:5
   |
10 |     foo(|| {
   |     ^^^ cannot infer type for type parameter `E` declared on the function `foo`

The naive observer might think "but this is the same as":

fn bar() {
    foo(|| {
        match qux() {
            Ok(()) => {},
            Err(e) => return Err(e),
        }
        Ok(true)
    });
}

which does compile, but the actual equivalent would be

fn bar() {
    foo(|| {
        match qux() {
            Ok(()) => {},
            Err(e) => return Err(e.into()),
        }
        Ok(true)
    });
}

which does fail to compile.

So my question would be: could type inference disambiguate these cases by considering the simplest Into::into that might be used, i.e. the identity?

Although I guess type annotations are not all that bad...

fn bar() {
    [1, 2].iter().try_find(|i| -> Result<_, &'static_str> {
        qux()?;
        Ok(true)
    });
}

Funnily enough, this does not happen in this case:

fn bar() -> Result<(), &'static str> {
    [1, 2].iter().try_find(|i| {
        qux()?;
        Ok(true)
    })?;
    Ok(())
}

But it does when changing the error type to e.g. String on both bar and qux.

I think that this will probably be solved if the current try trait v2 RFC is accepted since iirc that RFC chooses to forgo the into() call by default.

Wouldn't that mean that a whole lot of code that currently relies on that behavior would just flat-out break? If so, it seems like a major problem with that RFC.

Oh right, I was getting confused between the general case and the specific case of Result.

The general case doesn't but the specific case has to for the reason you said.

I wonder if there would be some way to have into() "prefer" doing no type changing if it is unbounded. Where the first into() call picks its receiver's type.

FWIW, it actually uses From. I tried switching to Into, but that made type inference even worse.

One possible way forward here would be the sketch outlined in https://github.com/scottmcm/rfcs/blob/do-or-do-not/text/0000-try-trait-v2.md#possibilities-for-try (but not a normative part of that RFC).

If we make unannotated try be the version that doesn't do error-conversion, then you'd be able to write that example as

fn bar() {
    foo(|| try {
        qux()?;
        true
    });
}

(This seems somewhat reasonable to me, as error-conversion fundamentally requires an annotation of some sort to know to what to convert.)

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