Skip
, takeIn Rust we can use the skip and take functions to advance to a position in an iterator. The clippy tool recommends skip and take.
With these functions, we narrow down the range of an iter()
. We usually need to call iter()
first to use skip and take. These functions affect loop performance.
Skip
exampleTo begin, we create an array of 4 numbers. We get an iterator from the array by calling iter()
in the for
-loop. We print out the values looped over.
skip()
with an argument of 2 on the return value of iter()
. This skips over 2 elements from the start.skip()
with an argument of 2, we access just the third and fourth elements, 30 and 40.fn main() { // Source array. let values = [10, 20, 30, 40]; // Loop starting past the first 2 items. for value in values.iter().skip(2) { println!("SKIP 2 FROM {:?} = {}", values, value); } }SKIP 2 FROM [10, 20, 30, 40] = 30 SKIP 2 FROM [10, 20, 30, 40] = 40
Take
With take()
we iterate over the first elements of a collection like an array. We specify the number of elements we wish to loop over.
fn main() { let numbers = [5, 10, 20, 40]; // Loop over first 2 numbers. for number in numbers.iter().take(2) { println!("TAKE 2: {number}"); } // Combine skip and take. for number in numbers.iter().skip(1).take(2) { println!("SKIP 1 TAKE 2: {number}"); } }TAKE 2: 5 TAKE 2: 10 SKIP 1 TAKE 2: 10 SKIP 1 TAKE 2: 20
Skip
benchmarkThe clippy tool recommends using skip and take instead of for-range
loops. But does this improve performance? We test skip and take performance here.
for
-loop over a range of indexes. It then tests the vector elements.for
-loop but uses skip and take instead of a range of indexes. It has the same effect as version 1.skip()
and take()
on a loop seems to reduce performance. Just accessing indexes directly is faster.use std::time::*; fn main() { if let Ok(max) = "20000".parse() { if let Ok(offset) = "20".parse::<usize>() { let top_offset = max - offset; // Sample vector. let mut source = vec![]; for _ in 0..max { source.push(0); } source[5000] = 1; let mut sum1 = 0; let mut sum2 = 0; // Version 1: range loop. let t0 = Instant::now(); for _ in 0..max { for i in offset..top_offset { if source[i] == 1 { sum1 += 1; } } } println!("{}", t0.elapsed().as_millis()); // Version 2: take and skip. let t1 = Instant::now(); for _ in 0..max { for element in source.iter().take(top_offset).skip(offset) { if *element == 1 { sum2 += 1; } } } println!("{}", t1.elapsed().as_millis()); println!("{} = {}", sum1, sum2); } } }222 249 20000 = 20000
In my tests, using skip and take with iter()
is slower than accessing indexes from a range directly. And using get_unchecked
with a range is the fastest approach.
A main goal of using Rust is to achieve excellent performance. With skip and take, we can narrow down a loop in a safe way. But unsafe code, and direct index accesses, may be faster.