Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Expected &-ptr, found tuple while iterating over an array of tuples

Tags:

rust

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?

like image 618
Syntactic Fructose Avatar asked Dec 15 '22 10:12

Syntactic Fructose


1 Answers

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.
like image 163
Lukas Kalbertodt Avatar answered Dec 19 '22 10:12

Lukas Kalbertodt