5

When iterating a list of tuples, the & is needed to make it work. Thus this will work ...

for &(a, b, c) in [("hello", 1.0, 5), ("world", 2.0, 2)].iter() {
    println!("{} {} {}", a, b, c);
}

but that won't ...

for (a, b, c) in [("hello", 1.0, 5), ("world", 2.0, 2)].iter() {
    println!("{} {} {}", a, b, c);
}

// type mismatch resolving `<core::slice::Iter<'_, (&str, _, _)> as core::iter::Iterator>::Item == (_, _, _)`:
// expected &-ptr,
found tuple [E0271]

I am sure it has to do with intricacies of the destructuring syntax that I have not yet fully internalised.

Can you explain which syntactical truth is behind the ampersand ?

Byron
  • 3,908
  • 3
  • 26
  • 35

1 Answers1

11

It's because the iter method for an array [T] returns an iterator that yields &T values. That's why the compiler says "expected &-ptr, found tuple [E0271]".

So why's that? Well, in general, you can't copy T. Unless the code assumes a more restrictive bound of T: Copy or T: Clone, it can only move values of type T.

This is a problem for arrays because there's no way to move a single element out of an array; doing so would invalidate the whole thing.

Aside: Vec and co. get around this by implementing additional logic in unsafe blocks to make it work. Containers may also provide into_iter which gives you an iterator that incrementally consumes the container, allowing you to move values out.

Because you want the array iter method to work for all arrays, it instead yields immutable references to each element in turn.

As a result, you're trying to destructure a &(&str, f32, i32), not a (&str, f32, i32), hence the additional &. Rust doesn't like implicitness, so you have to explicitly destructure the reference. This also helps make it clear that there's a dereference and a copy happening here.

DK.
  • 55,277
  • 5
  • 189
  • 162
  • Ok, but wait: Does it mean all my tuple members are actually copied into their respective slots a, b, and c ? I would have hoped I access them through a reference of the tuple. – Byron Feb 09 '15 at 09:44
  • 5
    They are copied. Actually, *in general*, they are copied if copyable, or moved if not behind a reference; failing that, the compiler will shout at you. If you want a reference, you have to *ask* for a reference. This is what the `ref` keyword is for: `&(ref a, ref b, ref c)`. This, in a pattern, says: "the thing at this position? Bind it *by reference* to a variable named `x`". And yes, you can use this anywhere a pattern is valid: in match arms, `if let` and regular `let`. Fun times! – DK. Feb 09 '15 at 16:15
  • 3
    @Byron: Oh! I almost forgot. You can also (with a recent `rustc`) remove the explicit call to `.iter()`. There is now an [`IntoIterator`](http://doc.rust-lang.org/std/iter/trait.IntoIterator.html) trait which goes from a thing to its iterator. Since you're iterating by-ref, change `[...].iter()` to `&[...]` (or `&mut [...]` for `[...].iter_mut()`). This also works with a vector `v` with `v`, `&v`, and `&mut v` being equivalent to `v.into_iter()`, `v.iter()` and `v.iter_mut()` respectively. – DK. Feb 09 '15 at 16:20
  • Thanks again for the clarification and for the updates on the new syntactical sugar. Sweet ! – Byron Feb 09 '15 at 18:06