The following compiles and works as expected:
// Example 1
for i in (0..10).filter(|i| i % 2 == 0) { println!("{} is even", i); }
I was expecting the following to work, too:
// Example 2
let is_even = |i| i % 2 == 0;
for i in (0..10).filter(is_even) { println!("{} is even", i); }
However, it fails to compile:
error[E0308]: mismatched types
--> src/main.rs:14:14
|
14 | for i in (0..10).filter(is_even) { println!("{} is even", i); }
| ^^^^^^^^^^^^^^^^^^^^^^^ one type is more general than the other
|
= note: expected type `for<'r> std::ops::FnMut<(&'r i32,)>`
found type `std::ops::FnMut<(&i32,)>`
The compiler complains that the argument of my closure has the wrong lifetime, the compiler expects a higher-rank trait bound (HRTB).
The reason is given in this answer:
Ultimately, this is caused due to limitations in Rust's type inference. Specifically, if a closure is passed immediately to a function that uses it, the compiler can infer what the argument and return types are. Unfortunately, when it is stored in a variable before being used, the compiler does not perform the same level of inference.
Hence, in theory there should probably not be much of a difference between Example 1 and Example 2, but in practice there is (due to technical limitations of the compiler).
Interestingly, the following code compiles and works correctly:
// Example 3
let is_even = |i: &i32| i % 2 == 0;
for i in (0..10).filter(is_even) { println!("{} is even", i); }
However, I don't understand how Example 3, which doesn't contain any explicit lifetime declarations, solves the lifetime issues the compiler complained about in Example 2.
In an answer to another question, the following explanation was given:
By explicitly marking the closure's single input parameter as a reference, the closure will then no longer generalize over the parameter type
T
, but over a higher-ranked lifetime of the reference input'a
in&'a _
.
However, simply marking the input parameter as a reference does not solve the issue, since the following code triggers the same compiler error:
// Example 4
let is_even = |&i| i % 2 == 0;
for i in (0..10).filter(is_even) { println!("{} is even", i); }
What's the precise difference of specifying &i
vs. i: &i32
here w.r.t. to the lifetime of the closure parameter?
I'm using rustc
1.46.0
.