rust iterators and closures

Closures

use std::thread;
use std::time::Duration;
 
fn generate_workout(intensity: u32, random_number: u32) {
    // defining a closure
    let expensive_closure = |num| {
        println!("calculating slowly...");
        thread::sleep(Duration::from_secs(2));
        num
    };
 
    if intensity < 25 {
        // calling a closure
        println!("Today, do {} pushups!", expensive_closure(intensity));
        println!("Next, do {} situps!", expensive_closure(intensity));
    } else {
        if random_number == 3 {
            println!("Take a break today! Remember to stay hydrated!");
        } else {
            println!(
                "Today, run for {} minutes!",
                expensive_closure(intensity)
            );
        }
    }
}
 
fn main() {
    let simulated_user_specified_value = 10;
    let simulated_random_number = 7;
 
    generate_workout(simulated_user_specified_value, simulated_random_number);
}
// type inference, the following are all the same:
fn  add_one_v1   (x: u32) -> u32 { x + 1 }
let add_one_v2 = |x: u32| -> u32 { x + 1 };
let add_one_v3 = |x|             { x + 1 };
let add_one_v4 = |x|               x + 1  ;
// this does not compile because the closure first infers the type
// of x as `String` and is then locked to `String`, hence it cannot
// be used with an integer
fn main() {
    let example_closure = |x| x;
 
    let s = example_closure(String::from("hello"));
    let n = example_closure(5);
}

Storing closures using generic parameters and the Fn traits

The Fn traits are provided by the standard library. All closures implement at least one of the traits: Fn, FnMut, or FnOnce.

use std::thread;
use std::time::Duration;
 
struct Cacher<T>
where
    T: Fn(u32) -> u32,
{
    calculation: T,
    value: Option<u32>,
}
 
impl<T> Cacher<T>
where
    T: Fn(u32) -> u32,
{
    fn new(calculation: T) -> Cacher<T> {
        Cacher {
            calculation,
            value: None,
        }
    }
 
    fn value(&mut self, arg: u32) -> u32 {
        match self.value {
            Some(v) => v,
            None => {
                let v = (self.calculation)(arg);
                self.value = Some(v);
                v
            }
        }
    }
}
 
fn generate_workout(intensity: u32, random_number: u32) {
    let mut expensive_result = Cacher::new(|num| {
        println!("calculating slowly...");
        thread::sleep(Duration::from_secs(2));
        num
    });
 
    if intensity < 25 {
        println!("Today, do {} pushups!", expensive_result.value(intensity));
        println!("Next, do {} situps!", expensive_result.value(intensity));
    } else {
        if random_number == 3 {
            println!("Take a break today! Remember to stay hydrated!");
        } else {
            println!(
                "Today, run for {} minutes!",
                expensive_result.value(intensity)
            );
        }
    }
}
 
fn main() {
    let simulated_user_specified_value = 10;
    let simulated_random_number = 7;
 
    generate_workout(simulated_user_specified_value, simulated_random_number);
}

Capturing the environment with closures

fn main() {
    let x = 4;
 
    // example of a closure that refers to a variable in its
    // enclosing scope
    let equal_to_x = |z| z == x;
 
    // we cannot use a function because functions are never allowed
    // to capture their environment
    //fn equal_to_x(z: i32) -> bool {
    //    z == x
    //}
 
    let y = 4;
 
    assert!(equal_to_x(y));
}

Closures can capture values from their environment in three ways, which directly map to the three ways a function can take a parameter: taking ownership, borrowing mutably, and borrowing immutably. These are encoded in the three Fn traits as follows:

  • FnOnce consumes the variables it captures from its enclosing scope, known as the closure’s environment. To consume the captured variables, the closure must take ownership of these variables and move them into the closure when it is defined. The Once part of the name represents the fact that the closure can’t take ownership of the same variables more than once, so it can be called only once.
  • FnMut can change the environment because it mutably borrows values.
  • Fn borrows values from the environment immutably.
// if you want to force the closure to take ownership of the
// values it uses in the environment, you can use the `move`
// keyword before the parameter list
fn main() {
    let x = vec![1, 2, 3];
 
    // x value is moved into the closure when the closure is
    // defined
    let equal_to_x = move |z| z == x;
 
    // compilation error because main no longer have ownership of x
    println!("can't use x here: {:?}", x);
 
    let y = vec![1, 2, 3];
 
    assert!(equal_to_x(y));
}

Processing a series of items with iterators

All iterators implement a trait named Iterator that is defined in the standard library.

// the definition of the trait looks like this
pub trait Iterator {
    type Item;
 
    fn next(&mut self) -> Option<Self::Item>;
 
    // methods with default implementations elided
}
#[cfg(test)]
mod tests {
    #[test]
    fn iterator_demonstration() {
        let v1 = vec![1, 2, 3];
 
        let mut v1_iter = v1.iter();
 
        assert_eq!(v1_iter.next(), Some(&1));
        assert_eq!(v1_iter.next(), Some(&2));
        assert_eq!(v1_iter.next(), Some(&3));
        assert_eq!(v1_iter.next(), None);
    }
}

Methods that consume the iterator

Methods that call next are called “consuming adaptors” because calling them uses up the iterator.

#[cfg(test)]
mod tests {
    #[test]
    fn iterator_sum() {
        let v1 = vec![1, 2, 3];
 
        let v1_iter = v1.iter();
 
        let total: i32 = v1_iter.sum();
 
        // we aren't allowed to use `v1_iter` after the call to
        // `sum` because `sum` takes ownership of the iterator
        assert_eq!(total, 6);
    }
}

Methods that produce other iterators

Other methods defined on the Iterator trait, know as “iterator adaptors”, allow you to change iterators into different kinds of iterators.

fn main() {
    let v1: Vec<i32> = vec![1, 2, 3];
 
    // calling `map` method to create a new iterator
    // calling `collect` method to consume the new iterator and
    // create a vector
    let v2: Vec<_> = v1.iter().map(|x| x + 1).collect();
 
    assert_eq!(v2, vec![2, 3, 4]);
}

Using closures that capture their environment

#[derive(PartialEq, Debug)]
struct Shoe {
    size: u32,
    style: String,
}
 
fn shoes_in_size(shoes: Vec<Shoe>, shoe_size: u32) -> Vec<Shoe> {
    // using `filter` method with a closure that captures
    // `shoe_size`
    shoes.into_iter().filter(|s| s.size == shoe_size).collect()
}

Using other Iterator trait methods

struct Counter {
    count: u32,
}
 
impl Counter {
    fn new() -> Counter {
        Counter { count: 0 }
    }
}
 
impl Iterator for Counter {
    type Item = u32;
 
    fn next(&mut self) -> Option<Self::Item> {
        if self.count < 5 {
            self.count += 1;
            Some(self.count)
        } else {
            None
        }
    }
}
 
#[cfg(test)]
mod tests {
    use super::*;
 
    #[test]
    fn calling_next_directly() {
        let mut counter = Counter::new();
 
        assert_eq!(counter.next(), Some(1));
        assert_eq!(counter.next(), Some(2));
        assert_eq!(counter.next(), Some(3));
        assert_eq!(counter.next(), Some(4));
        assert_eq!(counter.next(), Some(5));
        assert_eq!(counter.next(), None);
    }
 
    #[test]
    fn using_other_iterator_trait_methods() {
        let sum: u32 = Counter::new()
            .zip(Counter::new().skip(1))
            .map(|(a, b)| a * b)
            .filter(|x| x % 3 == 0)
            .sum();
        assert_eq!(18, sum);
    }
}

Iterator implementation is slightly faster than for loop implementation. Iterators are one of Rust’s zero-cost abstractions.