Generally the main thing to understand is that there is not one single FnOnce() type, rather every single closure in the program has its own unique type, and each such type implements the FnOnce() trait. The thing is that when dealing with code that uses closures, you need to deal with two situations:
- You want to talk about all closures. This is the job of generics.
- You want to talk about a specific closure. This is the job of existential types.
Generally to make matters more confusing, the impl Trait syntax is sometimes syntax sugar for generics, and sometimes the syntax used for existential types. Let's consider some examples.
fn decorator_with_generic_type<T: FnOnce()>(func: T) -> T {
|| {
println!("this function was called");
func();
}
}
In this particular case, you take one closure as an argument, and return some other closure as the return value. In this case you want the argument to be able to take any closure as argument, but the return type should always be the specific closure you specified in the function body, not some other closure from somewhere else. So you want generics for the argument and existentials for the return type. The example doesn't compile because you claim to return the same closure type as you were given as argument, but you actually return some other closure.
The way you use existential types is the impl Trait syntax, like this:
fn decorator_with_generic_type<T: FnOnce()>(func: T) -> impl FnOnce() {
|| {
println!("this function was called");
func();
}
}
This signature says "the function can take any closure type, let's call it T, and it will return some specific closure type".
Now what about your example with impl Trait in the argument position? Well that's where the confusing part comes in. When impl Trait is used as an argument instead of a return type, it instead becomes syntax sugar for generics. Thus your example is syntax sugar for my example above.
Generics are always generics and never an existential type. Your original function would compile if you had returned the same closure, because then the closure types really do match.
fn decorator_with_generic_type<T: FnOnce()>(func: T) -> T {
func
}
In structs you typically always want to use generics. It's not possible to use the impl Trait syntax in a struct directly. As for what type Foo = impl Trait means, well that's an existential type. This means that you can only ever have such a type alias refer to a single specific closure, for example trying to claim that two closures have the same existential type will fail to compile:
type MyFunc = impl FnOnce();
fn foo1() -> MyFunc {
|| {
println!("Hi!");
}
}
fn foo2() -> MyFunc {
|| {
println!("Hello :)");
}
}
error: concrete type differs from previous defining opaque type use
--> src/lib.rs:10:1
|
10 | fn foo2() -> MyFunc {
| ^^^^^^^^^^^^^^^^^^^ expected `[closure@src/lib.rs:5:5: 7:6]`, got `[closure@src/lib.rs:11:5: 13:6]`
|
note: previous use here
--> src/lib.rs:4:1
|
4 | fn foo1() -> MyFunc {
| ^^^^^^^^^^^^^^^^^^^
However if you make sure that its the same closure in both cases, it works:
type MyFunc = impl FnOnce();
fn foo1() -> MyFunc {
make_foo("Hi!")
}
fn foo2() -> MyFunc {
make_foo("Hello :)")
}
fn make_foo(s: &'static str) -> MyFunc {
move || {
println!("{}", s);
}
}