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

I’m wondering why f2 and f3 in main() below need a type annotation to compile but f1 does not? Is there any way to avoid the requirement of the type annotation in this trait?

Rust playground link

struct C<T> {
    data : T
}

trait MyTrait<TIn, F, TOut>
where
    F: Fn(TIn) -> TOut,
{
    fn g(x: Self, f: F) -> TOut;
}

impl<TIn, F, TOut> MyTrait<TIn, F, TOut> for C<TIn>
where
    F: Fn(TIn) -> TOut,
{
    fn g(x: C<TIn>, f: F) -> TOut {
        println!("1");
        f(x.data)
    }
}

impl<TIn, F, TOut> MyTrait<&TIn, F, TOut> for &C<TIn>
where
    F: Fn(&TIn) -> TOut,
{
    fn g(x: &C<TIn>, f: F) -> TOut {
        println!("2");
        f(&x.data)
    }
}

impl<TIn, F, TOut> MyTrait<&TIn, F, TOut> for C<TIn>
where
    F: Fn(&TIn) -> TOut
{
    fn g(x: C<TIn>, f: F) -> TOut {
        println!("3");
        f(&x.data)
    }
}


fn main() {
    let x1 : C<u32> = C{data : 1}; 
    let x2 : C<u32> = C{data : 2};
    let x3 : C<u32> = C{data : 3};
    
    let f1 = |x| x;
    let f2 : fn(&u32) -> u32 = |&x| x;
    let f3 : fn(&u32) -> u32 = |&x| x;
    
    let y1 = MyTrait::g(x1, f1);
    let y2 = MyTrait::g(&x2, f2);
    let y3 = MyTrait::g(x3, f3);
}

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: