I understand that .copied() is used to take ownership of values and remove references so that .filter() can operate correctly. I could also use .into_iter() to achieve the same effect.
This works as expected:
fn main() {
let numbers = [5, 10, 15, 20];
let filtered: Vec<i32> = numbers.iter()
.copied()
.filter(|x| x % 2 == 0)
.collect();
println!("{:?}", filtered); // Output: [10, 20]
}
However, Why this does not work as expected?
fn main() {
let numbers = [5, 10, 15, 20];
let filtered: Vec<i32> = numbers.iter()
.copied()
.filter(|x| x > 2)
.collect();
println!("{:?}", filtered); // Expected: [5, 10, 15, 20], but something seems off.
}
Additionally, .map() seems to work fine with anything, unlike .filter().
Could someone help clarify why .filter() behaves differently in these cases, and why .map() doesn’t have the same issue?
By calling [..].iter() you create an iterator over borrowed values &i32. When again calling .copied() you copy the iterator over said borrowed values. This means in both cases we are iterating over values of type &i32.
The % operator seems to work just fine with borrowed values while a comparison with > 2 requires a value rather than a reference.
You simply need to dereference the value x in your second example in order to work.
The function passed to filter() always takes a reference, since it isn't supposed to take ownership of or change the items, only inspect them. The function passed to map(), on the other hand, is supposed change the items, and outputs the thing it maps to so it makes sense for it to take ownership.
The error message your code generates also suggests a simple fix: either dereference x or reference the 2 so that they have the same type.
The reason for your problem is that Iterator::filter’s closure takes &Iterator::Item, which means the closure always gets a reference, even if the iterator is over values. (Also see Why Does .filter() dont work? - #4 by jameseb7)
Yes, I got that from the error, but what's the logic? Just a simple change in a condition caused this error. What else I need to be aware of along these lines, as this seems quite unusual
That's not true: copied() copies the items iterated over, not the iterator, so the iterator after .copied() is over values of type i32.
It's just that x is of type &i32 and 2 is if type i32, and comparison operators are generally only implemented on things of the same type. The % operator, on the other hand, is implemented for all the combinations of &T and T. The reason for this isn't clear though.
Compiling playground v0.0.1 (/playground)
error[E0308]: mismatched types
--> src/main.rs:6:25
|
6 | .filter(|x| x > 2)
| ^ expected `&_`, found integer
|
= note: expected reference `&_`
found type `{integer}`
help: consider dereferencing the borrow
|
6 | .filter(|x| *x > 2)
| +
For more information about this error, try `rustc --explain E0308`.
error: could not compile `playground` (bin "playground") due to 1 previous error
When you have an iterator of type T, filter takes a FnMut(&T) -> bool, which means that your closure's x argument has type &i32. &i32 and i32 are not comparable, but dereferencing the x argument produces an i32, which is comparable with another i32.
This doesn't happen with your |x| x % 2 == 0 example because the stdlib has an implementation of Rem<&i32> for i32, which is used for the x % 2 part of your filter function.
Adding the suggested asterisk produces a program that prints [5, 10, 15, 20], as you expect.
No, you don't need to use copied to make filter operate correctly. You can dereference the values in the closure when needed, and comparisons can be done with references. It seems that you're not aware of the types of the variables during the iteration.
I suggest looking at the type of the variables you're using. If you're using rust-analyzer in your editor, you can "hover" over the variable to see the type. This will show you whether the variables are a reference (or not) so you can easily see whether they need to be dereferenced.
And of course the doc and the compiler error will also tell you this information. Another approach is to add type annotations to the variables with the type you expect, to see whether this causes a compiler error or not.
If you're not using rust-analyzer, I strongly recommend it. You'll see the compiler errors in your editor, which speeds up the process of finding the problems and fixing them.