Closures and Iterators

Closures

Rust's closures are anonymous functions you can save in a variable or pass as arguments to other functions.


#![allow(unused)]
fn main() {
let x = vec![1, 2, 3, 4]
    .iter()
    .map(|x| x + 3);
}

To define a closure, we start with a pair of vertical pipes (|), inside which we specify the parameters to the closure. After the parameters, we place curly brackets that hold the body of the closure – these are optional if the closure body is a single expression.

Closures don't require you to annotate the types of the parameters or the return value like fn functions do. But as with variables, we can add type annotations of we want to increase explicitness and clarity at the cost of being more verbose than is strictly necessary.


#![allow(unused)]
fn main() {
let closure_annotated = |i: i32| -> i32 { i + 1 };
let closure_inferred = |i | i + 1 ;
}

We can define a closure and store the closure in a variable rather than storing the result of the function call.

The first time we call a closure with any value, the compiler infers the type of it and the return type of the closure. Those types are the locked into the closure, and we get a type error if we try to use a different type with the same closure.

Capturing the Environment with Closures

Closures have an additional capability that functions don`t have: they can capture their environment and access variables from the scope in which they are defined.


#![allow(unused)]
fn main() {
let x = 4;
let equal_to_x = |z| 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.

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

TraitDescription
FnOnceconsumes the variables it captures from its enclosing scope, known as 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 represents the fact that the closure can not take ownership more than once, so it can be called only once.
FnMutcan change the environment be it mutably borrows values
Fn borrows value from the environment immutability

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.


#![allow(unused)]
fn main() {
let equal_to_x = move |z| z == x;
}

Iterators

The iterator pattern allows you to perform some task on a sequence of items in turn. An iterator is responsible for the logic of iterating over each item and determining when the sequence has finished.

In Rust, iterators are lazy, meaning they have no effect until you call methods that consume the iterator to use it up.


#![allow(unused)]
fn main() {
let v = vec![1, 2, 3];
let v_iter = v.iter();

for val in v_iter {
	// -- snip --
}
}

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


#![allow(unused)]
fn main() {
pub trait Iterator {
	type Item;

	fn next(&mut self) -> Option<Self::Item>;

	// -- snip --
}
}

Notice this definition uses some new syntax: type Item and Self::Item, which are defining an associated type with this trait. The Item type will be the type returned from the iterator.


#![allow(unused)]
fn main() {
let v = vec![1, 2, 3];
let mut v_iter = v.iter();

assert_eq!(v_iter.next(), Some(&1));
assert_eq!(v_iter.next(), Some(&2));
assert_eq!(v_iter.next(), Some(&3));
assert_eq!(v_iter.next(), None);
}

We need to make iterator mutable: calling the next method on an iterator changes internal state that the iterator uses to keep track of where it is in the sequence.

We did not need to make v_iter mutable when we used a for loop because the loop took ownership of v_iter and made it mutable behind the scenes.

The iter method produces an iterator over immutable references. If we want to create an iterator that takes ownership of v and returns owned values, we can call into_iter instead of iter. Similarly, if we want to iterate over mutable references, we can call iter_mut instead of iter.

Creating our own Iterators

The Iterator trait has a number of different methods with default implementations provided by the standard library. Other methods defined on the Iteratortrait, known as iterator adaptors, allow you to change iterators into different kinds of iterators.


#![allow(unused)]
fn main() {
struct Counter {
	count: u32,
}

impl Counter {
	fn new() -> Self {
		Counter { count: 0 }
	}
}

impl Iterator for Counter {
	type Item = u32;

	fn next(&mut self) -> Option<Self::Item> {
		self.count += 1;

		if self.count < 6 {
			Some{self.count}
		} else {
			None
		}
	}
}
}

We implemented the Iterator trait by defining the next method, so we can now use any Iterator trait method's default implementations as defined in the standard library, because they all use the next method's functionality.


#![allow(unused)]
fn main() {
let sum: u32 = Counter::new().zip(Counter::new().skip(1))
							 .map(|(a, b)| a * b)
							 .filter(|x| x % 3 == 0)
							 .sum();
asset_eq!(sum, 18);
}