Closures

Closures

Rust by example - Closures

fn find_emails(list: Vec<String>) -> Vec<String> {
    list.into_iter()
        .filter(|s| s.contains('@')) // <-- closure
        .collect()
}

fn main() {
    for s in find_emails(vec![
        String::from("example"),
        String::from("example@example.com"),
    ]) {
        println!("{}", s);
    }
}

Closure with type annotations

use std::thread;
use std::time::Duration;

fn main() {
    // closure can use type annotation. Multiple statements can be
    // enclosed in a block.
    let _expensive_closure = |num: u32| -> u32 {
        println!("calculating slowly...");
        thread::sleep(Duration::from_secs(2));
        num
    };
}

Closures can capture variables

  • by reference: &T
  • by mutable reference: &mut T
  • by value: T

They preferentially capture variables by reference and only go lower when required.

To force a move:

use std::thread;

fn main() {
    let list = vec![1, 2, 3];
    println!("Before defining closure: {:?}", list);

    // `move` forces the closure to take ownership of the values it uses.
    thread::spawn(move || println!("From thread: {:?}", list))
        .join()
        .unwrap();
}

Closures as input parameters


// A function which takes a closure as an argument and calls it.
// <F> denotes that F is a "Generic type parameter"
fn apply<F>(f: F)
where
    F: FnOnce(),
{
    // The closure takes no input and returns nothing.
    // could also be `Fn` or `FnMut`.
    f();
}

// A function which takes a closure and returns an `i32`.
fn apply_to_3<F>(f: F) -> i32
where
    // The closure takes an `i32` and returns an `i32`.
    F: Fn(i32) -> i32,
{
    f(3)
}

fn main() {
    apply(|| println!("applied"));
}
  • std::ops::Fn⮳: the closure uses the captured value by reference (&T)
  • std::ops::FnMut⮳: the closure uses the captured value by mutable reference (&mut T)
  • std::ops::FnOnce⮳: the closure uses the captured value by value (T)

Functions may also be used as arguments.