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
-- such as the ability to take &String
with any lifetime -- cannot be exercised, even if the hidden type which is returned has them.
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));
}