FnOnce becomes "not general enough" after going through identity

Surprisingly, this doesn't compile:

fn foo(_: &impl FnOnce(&String)) {}

fn identity<T, U>(t: impl FnOnce(T) -> U) -> impl FnOnce(T) -> U {
    t
}

fn main() {
    let closure = |_: &String| {};
    foo(&closure);
    foo(&identity(closure));
}
error: implementation of `FnOnce` is not general enough
  --> src/main.rs:10:5
   |
10 |     foo(&identity(closure));
   |     ^^^^^^^^^^^^^^^^^^^^^^^ implementation of `FnOnce` is not general enough
   |
   = note: `impl FnOnce(&'2 String)` must implement `FnOnce<(&'1 String,)>`, for any lifetime `'1`...
   = note: ...but it actually implements `FnOnce<(&'2 String,)>`, for some specific lifetime `'2`

I know that it would compile if I defined identity as:

fn identity<T>(t: T) -> T {
    t
}

But that wouldn't replicate the problem I'm having in my real code.

in this case where the FnOnce takes a reference as argument, you can do:

fn identity<T, U>(t: impl FnOnce(&T) -> U) -> impl FnOnce(&T) -> U {
    t
}

which is short for:

fn identity<T, U>(t: impl for<'a> FnOnce(&'a T) -> U) -> impl for<'a> FnOnce(&T) -> U {
    t
}

Type parameters can only resolve to a single type -- so in the example, identity takes and returns a closure that takes &'a String for one specific lifetime 'a, not any lifetime. (Types that only differ by lifetime are still distinct types.)

Additionally, that's not an identity function. You return an opaque type which is distinct from the input type, for all type checking outside of the function body. Any abilities not reflected in the opaque impl[1] -- such as the ability to take &String with any lifetime -- cannot be exercised, even if the hidden type which is returned has them.


  1. except autotraits â†Šī¸Ž

1 Like

Why are you declaring two separate impl's, in the first place?

fn foo(_: &impl FnOnce(&String)) {}

fn identity<T, U, C>(t: C) -> C
where C: FnOnce(T) -> U {
    t
}

fn main() {
    let closure = |_: &String| {};
    foo(&closure);
    // compiles just fine
    foo(&identity(closure));
}