Again, I want to start with the same example as in that answer. Compare this:
fn show_both_1<S: Show>(x: S, y: S) {
println!("{:?} {:?}", x, y);
}
and this:
fn show_both_2<S1: Show, S2: Show>(x: S1, y: S2) {
println!("{:?} {:?}", x, y);
}
(now using {:?}
instead of {}
because of the recent changes)
The first function requires that both arguments must have the same type, even though this type can be arbitrary as long as it implements Show
:
show_both_1::<i32>(1i32, 2i32); // ok
show_both_1::<f64>(1.0f64, 2.0f64); // ok
show_both_1::<???>(1i32, 2.0f64); // not ok!
Obviously the last function call does not make sense, because types of the arguments are different, but the function wants them to have the same type. You can't even write the type parameter explicitly - should it be i32
or f64
?
The second function allows different types, so all of these calls are ok:
show_both_2::<i32, i32>(1, 2);
show_both_2::<f64, f64>(1.0, 2.0);
show_both_2::<i32, f64>(1, 2.0);
Now for each argument a different type parameter is used, so it is perfectly fine to pass values of different types, as long as both of these types implement Show
.
Absolutely the same thing happens with closures. For each closure the compiler generates a new unique type which implements one of Fn*
traits. These types are anonymous, so you can't name them:
let f: ??? = |&: x: i32, y: i32| x + y;
There is nothing you can write instead of ???
above, but there is no need to because the compiler knows which type it has generated for the closure and so it can infer f
's type. What really does matter is that this anonymous type will always implement one of special traits: Fn
, FnMut
or FnOnce
. Consequently if you want your function to accept a closure, you need to pass it an instance of some type which implements one of these traits.
But this is natural job for generics! They are usually used when you want your function to accept some arbitrary type which implements some known trait, and situation with closures is absolutely the same. So you have this:
fn call_closure<F: FnMut(i64) -> bool>(f: F) -> bool {
f(10)
}
Because this function argument has generic type, this function can be used with any type which implements FnMut(i64) -> bool
trait (which is just a shorthand for FnMut<(i64,), bool>
, including anonymous types for closures generated by the compiler:
call_closure(|x| x > 10);
call_closure(|x| x == 42);
The compiler will generate a unique type for each of these closures, but since these generated types will implement FnMut(i64) -> bool
trait, call_closure
will happily accept both of them.
The situation with different type parameters which I described in the beginning naturally extends to closures because the same mechanism is used here, that is, traits.
fn call_closures_2<F: FnMut(i64) -> bool>(f1: F, f2: F) -> bool {
f1(10) && f2(20)
}
This function accepts two arguments which must be of the same type as long as this type implements FnMut(i64) -> bool
trait. And this means that this invocation won't work:
call_closures_2(|x| x > 9, |x| x == 20)
It won't work because these closures have unique, i.e. different types, but the function requires that the types must be the same. For example, this does work:
fn call_closures_3<F: Fn(i64) -> bool>(f1: &F, f2: &F) -> bool {
f1(10) && f2(20)
}
let f = |&: x: i64| x == 10;
call_closures_3(&f, &f);
Note that the function arguments must still be of the same type (now references for the convenience of the example), but since we call it with references to the same closure, their type is the same, and everything works fine. This is not very useful though because it is very limiting - usually you want to provide different closures to functions which takes several ones.
For this reason the function needs separate type parameters in order to accept different closures:
fn call_closures_4<F1, F2>(f1: F1, f2: F2) -> bool
where F1: FnMut(i64) -> bool,
F2: FnMut(i64) -> bool {
f1(10) && f2(20)
}
call_closures_4(|x| x >= 9, |x| x <= 42)
Now type parameters are independent, and even though closures have different anonymous types, it is ok to call this function with them: F1
will become the generated type of the first closure and F2
will become the generated type of the second closure.