Closures

Capturing Variables

Closures that Borrow

Simple example:


#![allow(unused)]
fn main() {
fn sort_by_statistic(cities: &mut Vec<City>, stat: Statistic) {
    cities.sort_by_key(|city| -city.get_statistic(stat));
}
}

Closures that Steal

Say we wanted to create a function that sorts a list of cities in a separate thread. It might look something like this:


#![allow(unused)]
fn main() {
fn start_sorting_thread(mut cities: Vec<City>, stat: Statistic)
    -> thread::JoinHandle<Vec<City>>
{
    // take ownership of stat
    let key_fn = move |city: &City| -> i64 { -city.get_statistic(stat) };

    // take ownership of cities and key_fn
    thread::spawn(move || {
        cities.sort_by_key(key_fn);
        cities
    })
}
}

In the above example, we had to add the move keyword before each closure.

Keyword: The move keyword tells Rust that a closure doesn't borrow the variables it uses: it steals them.

Rust therefore offers two ways for closures to get data from enclosing scopes:

  1. Moves
  2. Borrowing

Function and Closure Types

Structs may have function-typed fields.

In memory, function values are just the memory address of the function's machine code.

A function can take another function as an argument:


#![allow(unused)]
fn main() {
// Given a list of cities and a test function,
// return the number of cities that returned true from the test
fn count_selected_cities(
    cities: &Vec<City>,
    test_fn: fn(&City) -> bool,
) -> usize {
    let mut count = 0;
    for city in cities {
        if test_fn(city) {
            count += 1;
        }
    }
    count
}
}

Concept: Closures do not have the same type as functions!

Term: A value of type fn(&City) -> bool is called a function pointer.

The count_selected_cities's type signature must be changed if test_fn should be a closure instead of a function value:


#![allow(unused)]
fn main() {
fn count_selected_cities<F>(cities: &Vec<City>, test_fn: F) -> usize
    where F: Fn(&City) -> bool
{
    let mut count = 0;
    for city in cities {
        if test_fn(city) {
            count += 1;
        }
    }
    count
}
}

We've now genericized count_selected_cities. It'll accept test_fn of any type F, as long as F implements the special trait Fn(&City) -> bool.

Concept: Every closure has its own type, because a closure may contain data: values either borrowed or stolen from enclosing scope. So, every closure has an ad hoc type created by the compiler. But, every closure implements the Fn trait.

Closure Performance

Closures aren't allocated on the heap unless you put them in a Box, Vec, or other container.

Closures and Safety

Closures that Kill

Basically, double free errors are impossible in Rust.

FnOnce

Concept: Closures that drop values are not allowed to have Fn. Instead, they implement FnOnce, the trait of closures that can only be called once. The first time you call a FnOnce closure, the closure itself is used up.

FnMut

Concept: Closures that require mut access to a value, but don't drop any values, are FnMut closures.

Summary

These are the three categories of closures, in order of most broad to least:

TraitDescription
FnOnceCan only be called once, if the caller owns the closure.
FnMutCan be called multiple times if the closure itself is declared mut.
FnCan be called multiple times without restriction and also encompasses all fn functions.

Callbacks

Here's an example program that implements a basic router:


#![allow(unused)]

fn main() {
struct Request {
    method: String,
    url: String,
    headers: HashMap<String, String>,
    body: Vec<u8>,
}

struct Response {
    code: u32,
    headers: HashMap<String, String>,
    body: Vec<u8>,
}

type RouteCallback = Box<Fn(&Request) -> Response>;

struct BasicRouter {
    routes: HashMap<String, RouteCallback>,
}

impl BasicRouter {
    fn new() -> BasicRouter {
        BasicRouter { routes: HashMap::new() }
    }

    fn add_route<C>(&mut self, url: &str, callback: C)
        where C: Fn(&Request) -> Response + 'static
    {
        self.routes.insert(url.to_string(), Box::new(callback))
    }

    fn handle_request(&self, request: &Request) -> Response {
        match self.routes.get(&request.url) {
            None => not_found_response(),
            Some(callback) => callback(request),
        }
    }
}

}