I have an array:
const adjacent: [(i8, i8); 8] = [(-1, -1), (-1, 0), (-1, 1), (-1, 0), (1, 0), (1, 1), (1, 0), (1, -1)];
This array represents all adjacent neighbors of a cell within a ROWxCOLUMN grid. To iterate over this array to find all neighbors, I do
for k in adjacent.into_iter() { let (i, c) = (k.0, k.1); if let Some(a) = grid.get(r+i, j+c) { /* ... */ } }
The second line seems like it could be substituted for K, but this causes an error if you write for (i, c) in adjacency.into_iter() { ...
error: type mismatch resolving `<core::slice::Iterator>::Item == (_, _)`: expected &-ptr found tuple
what's going on here? Can someone explain why I cannot do this?
The solution
There is quite something going on there. First the working code:
for &(i, c) in &adjacent { }
More detailed explanation follows.
The Problem
Your iterator is spitting out items of the type &(i8, i8)
which are references to the actual data. You tried to destructure it with the (i, c)
pattern. This doesn't work because those are two different types; namely a reference to a tuple and a tuple. Hence, if you add the &
to your pattern, the type of the pattern and the item match and the compiler can happily destructure it for you.
How to solve this on your own the next time
The full error message is:
<anon>:11:5: 11:42 error: type mismatch resolving `<core::slice::Iter<'_, (i8, i8)> as core::iter::Iterator>::Item == (_, _)`: expected &-ptr, found tuple [E0271] <anon>:11 for (i, c) in adjacent.into_iter() {} ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This error message could actually be improved, but nevertheless: Let's dig into it. The compiler says it can't resolve an actual type (on the left, core::slice::Iter<'_, (i8, i8)>
) as another type, that was somehow "requested" (on the right, <core::iter::Iterator>::Item == (_, _)
).
You "requested" an Iterator with the item type (_, _)
, which makes sense because that's exactly what you wrote: (i, c)
. And what about the type on the left? If we look it up in the documentation we can see that it implements the Iterator
trait with the type Item = &'a T
. We can ignore the explicit lifetime 'a
and just notice that it's a reference to the type parameter T
. But we know what T
is by looking at the error message: core::slice::Iter<'_, (i8, i8)>
. So T
is (i8, i8)
and thus the item type of the iterator is &(i8, i8)
.
About IntoIterator
and for loops
You used an explicit .into_iterator()
call on the array. It's understandable why you did that: for _ in adjacent
fails with the message
the trait `core::iter::Iterator` is not implemented for the type `[(i8, i8); 8]`
This is confusing: Why wouldn't you be able to iterate over an array of size 8? Indeed, to understand it, you have to know about how for
loops work.
The "thing" you want to iterate over has to either implement Iterator
directly or IntoIterator
. Most data structures (arrays, Vec
, ...) don't implement Iterator
directly, but IntoIterator
. The array
primitive does implement IntoIterator
, too -- kinda. If we take a look at the documentation, we can see that there are two implementations of IntoIterator
for each array length:
impl<'a, T> IntoIterator for &'a [T; 8] type Item = &'a T impl<'a, T> IntoIterator for &'a mut [T; 8] type Item = &'a mut T
Notice how this trait is not implemented for the array type directly, but for a reference to it. Also these implementations have a reference as the Item
type. So why isn't IntoIterator
implemented for [T; 8]
directly? Because we cannot move out of an array.
To fix this we write &adjacent
instead of adjacent
-- a reference to the array. Then the compiler will find the correct implementation. Why does the manual into_iterator
call work? Well... that's a whole different story, but in short: the .
method call converts between value and reference types on it's own.
TL;DR: So if you have a data structure adjacent
of some type (array, Vec
, ...), you basically have three possibilities:
for _ in &adjacent
: You want to iterator over immutable references to the items. This is what you want most of the time! for _ in &mut adjacent
: You want to iterator over mutable references in order to modify the items. for _ in adjacent
: You want to take ownership of the items and thus make the data structure unusable afterwards. The least common option! Doesn't even work for some data structures.
The error says it is expecting a pointer, so you have to use &
in the pattern:
for &(i, c) in adjacent.into_iter() { let (i, c) = (k.0, k.1); if let Some(a) = grid.get(r+i, j+c) { /* ... */ } }
This specific for
loop does not take ownership of the elements, it takes ownership of the references.
We can see a better error with the following, equivalent, code:
let mut it = adjacent.into_iter(); loop { match it.next() { Some((i, c)) => println!("{}, {}", i, c), None => break, } } src/main.rs:7:18: 7:24 error: mismatched types: expected `&(i8, i8)`, found `(_, _)` (expected &-ptr, found tuple) [E0308] src/main.rs:7 Some((i, c)) => println!("{}, {}", i, c), ^~~~~~