58

I'm trying to write a program that involves filtering and folding over arrays. I've been using The Rust Programming Language, first edition as a reference, but I don't understand what happens when I form iterators over arrays. Here is an example:

fn compiles() {
    let range = (1..6);
    let range_iter = range.into_iter();
    range_iter.filter(|&x| x == 2);
}

fn does_not_compile() {
    let array = [1, 4, 3, 2, 2];
    let array_iter = array.into_iter();
    //13:34 error: the trait `core::cmp::PartialEq<_>` is not implemented for the type `&_` [E0277]
    array_iter.filter(|&x| x == 2);
}

fn janky_workaround() {
    let array = [1, 4, 3, 2, 2];
    let array_iter = array.into_iter();
    // Note the dereference in the lambda body
    array_iter.filter(|&x| *x == 2);
}

(Rust playground)

In the first function, I follow that the iterator over the range does not take ownership, so I must take a &x in filter's lambda, but I don't understand why the second example with the array behaves differently.

trent
  • 25,033
  • 7
  • 51
  • 90
WillEngler
  • 745
  • 2
  • 7
  • 8

3 Answers3

50

In cases like this, it's very useful to force the compiler to tell you the type of the variable. Let's trigger a type error by assigning the closure argument to an incompatible type:

array_iter.filter(|x| { let _: () = x; x == 2 });

This fails with:

error[E0308]: mismatched types
 --> src/lib.rs:4:41
  |
4 |     array_iter.filter(|x| { let _: () = x; x == 2 });
  |                                    --   ^ expected `()`, found `&&{integer}`
  |                                    |
  |                                    expected due to this

Now we know the type of x is a &&{integer} - a reference to a reference to some kind of integer. We can then match against that instead:

fn hooray() {
    let array = [1, 4, 3, 2, 2];
    let array_iter = array.into_iter();
    array_iter.filter(|&&x| x == 2);
}

The question now becomes "why is it a reference to a reference"? The short version is that the iterator of an array returns references (see the type Item = &'a T part). In addition, Iterator::filter passes a reference to the closure to prevent moving and subsequently losing non-Copy types.

In Rust 1.51, you can use array::IntoIter to get a by-value iterator:

fn hooray() {
    let array = [1, 4, 3, 2, 2];
    let array_iter = std::array::IntoIter::new(array);
    array_iter.filter(|&x| x == 2);
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • 1
    Thank you! I picked this answer because it walked me through how I might have used the compiler to figure it out on my own. I also appreciated the links to learn more. – WillEngler May 26 '15 at 19:54
36

Arrays are the type [T; N] in Rust, for any element type T and a constant number N. It's a fixed size array.

Rust doesn't implement IntoIterator for arrays at the moment. All arrays coerce to slices (type [T]) and the slice methods are available on the array because of this. The arrays also get the slice's iterator, which is called std::slice::Iter<'a, T> and has elements of type &'a T: it iterates by reference!

This is why into_iter() on a Range<i32> produces an iterator of i32 and into_iter() on a [i32; 5] produces an iterator of &i32.

If you need by value iterators for arrays and

  • You are using Rust 1.51 or newer, you can use array::IntoIter:

    fn does_now_compile() {
        let array = [1, 4, 3, 2, 2];
        let array_iter = std::array::IntoIter::new(array);
        array_iter.filter(|&x| x == 2);
    }
    
  • You are using older versions of Rust, by-value iterators have been implemented in the broader ecosystem, see arrayvec and literator.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
bluss
  • 12,472
  • 1
  • 49
  • 48
3

As Shepmaster and bluss said, you can check the documentation for the array type, which mentions:

Arrays of sizes from 0 to 32 (inclusive) implement the following traits if the element type allows it:

  • IntoIterator (implemented for &[T; N] and &mut [T; N])

As it says, this is only for references, and is reflected in its Item type: type Item = &'a T and type Item = &'a mut T.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Boris
  • 109
  • 6
  • The maximum length of 32 is generally lifted as of [Rust 1.47](https://github.com/rust-lang/rust/blob/1.47.0/RELEASES.md#libraries). – Shepmaster Mar 29 '21 at 14:25