Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to iterate over and filter an array?

Tags:

rust

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.

like image 821
WillEngler Avatar asked May 26 '15 19:05

WillEngler


People also ask

How do you filter data from an array?

One can use filter() function in JavaScript to filter the object array based on attributes. The filter() function will return a new array containing all the array elements that pass the given condition. If no elements pass the condition it returns an empty array.

Does array filter iterate?

The JavaScript Array. The filter() method takes in a callback function and calls that function for every item it iterates over inside the target array.

Is it possible to iterate over arrays?

Iterating over an arrayYou can iterate over an array using for loop or forEach loop. Using the for loop − Instead on printing element by element, you can iterate the index using for loop starting from 0 to length of the array (ArrayName. length) and access elements at each index.


3 Answers

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);
}
like image 171
Shepmaster Avatar answered Oct 20 '22 12:10

Shepmaster


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.

like image 34
bluss Avatar answered Oct 20 '22 13:10

bluss


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.

like image 3
Boris Avatar answered Oct 20 '22 13:10

Boris