Why are type annotations required for calling this trait in some circumstances?

Here is a more minimal example, that illustrates the problem at hand:

fn foo<F> (_: F)
where
    F : FnOnce(&u32) -> u32,
{}

fn bar<'r, F> (_: F)
where
    F : FnOnce(&'r u32) -> u32,
{}

fn main ()
{
    // infers a single but unknown lifetime
    let f = |&x: /* &'? */ _| -> u32 {
        x
    };
    foo(f); // fails: f must be callable with a borrow of any lifetime
    bar(f); // ok
}

fn foo_unsugared<F> (_: F)
where
    for<'r>
        F : FnOnce(&'r u32) -> u32
    ,
{}

The problem lies with "type inference not leading to lifetime elision":

  • foo expects a closure / callable taking a borrow of a u32.
    But for hownlong is it borrowed? What lifetime 'r in &'r u32? Since there is no lifetime parameter, it assumes HRTB (see foo_unsugared): the input closure must be callable on an integer borrowed for a lifetime of any size. That is, it requires that F offer an infinite family of callable interfaces.

  • the f closure in main, however, takes an argument whose type is inferred: &x: ?. Since it returns a u32 after a destructuring &x pattern match, Rust infers that the input type is &'? u32. That is, a borrow of a u32 with a single (but unknown / to-be-inferred) lifetime.

  • it does thus not offer the infinite family of callable interfaces that foo expects from its input; hence the error.

    • bar, for instance, only requires (any) one callable interface, and thus accepts f.

Funnily enough, by making Rust start inferring the type of the parameter with a borrow "hint", & _, the borrow part in the type is not inferred and does thus follow the classic elision rules, with an implicit universal lifetime parameter: it compiles just fine :slight_smile: