Closures

Rust’s closures are anonymous functions you can save in a variable or pass as arguments to other functions. Closures can be created in one place and be called elsewhere to evaluate it in a different context and closures can capture values from the scope in which they’re defined.

Capturing the Environment with Closures

Closures doesn’t usually require type annotation like fn since they aren’t used in an exposed interface: they are stored in variables and used without naming them and exposing them to users.

Closures are typically short and relevant only within a narrow context rather than in any arbitrary scenario, so the Rust compiler can infer the types of the parameters and the return type. However, we can add type annotations if we want.

let expensive_closure = |num: u32| -> u32 {
    println!("calculating slowly...");
    thread::sleep(Duration::from_secs(2));
    num
};

// however, a simplest kind of closure can look like this
let add_one = |x| x+1;

// the type is only inferred once
let reflect = |x| x;
let s = reflect(String::from("hello")); // ok, s will be "hello"
let n = reflect(1); // ERROR, since the param type is set to string

Capturing References or Moving Ownership

Rust will decide how the closure capture the variables: either through immutable reference, mutable reference or taking ownership. We can also specify them explicitly as well

let only_borrow = || println("var is {:?}", var)
let mut borrow_mutably = || var.change();
// taking ownership example: thread
thread::spawn(move || do_something(var)) // move is specified before closure
	.join()
	.unwrap();

Once a closure captured a reference or ownership of a value from the environment, it can decide what to do with it: move a captured value out of the closure, mutate the captured value, neither move nor mutate, or capture nothing from the environment to begin with

The way a closure captures and handles values from the environment affects which traits the closure implements, and traits are how functions and structs can specify what kinds of closures they can use. Depending on how the closure’s body handles the values, closures will automatically implement one, two or all three of the Fn traits:

  1. FnOnce applies to closures that can be called once. All closures implement at least this trait. A closure that moves captured values out of its body will only implement this trait and not other Fn traits.
  2. FnMut applies to closures that don’t move captured values out of their body, but that might mutate the captured values. These closures can be called more than once.
  3. Fn applies to closures that don’t move captured values out of their body and that don’t mutate captured values, as well as closures that capture nothing from the environment. These closures can be called more than once without mutating their environment.

Processing a Series of Items with Iterators

The iterator pattern allows you to perform some task on a sequence of items in turn. It 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.

The Iterator Trait and the next method

All iterators implement a trait named Iterator that is defined in the standard library, and the definition looks like this

pub trait Iterator {
	type Item;
	fn next(&mut self) -> option<Self::Item>;
  // ...
}

let v1 = vec![1,2,3];
let v1_iter = v1.iter();
v1_iter.next(); // Some(&1)