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:
- Moves
- 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 implementFnOnce
, the trait of closures that can only be called once. The first time you call aFnOnce
closure, the closure itself is used up.
FnMut
Concept: Closures that require
mut
access to a value, but don't drop any values, areFnMut
closures.
Summary
These are the three categories of closures, in order of most broad to least:
Trait | Description |
---|---|
FnOnce | Can only be called once, if the caller owns the closure. |
FnMut | Can be called multiple times if the closure itself is declared mut . |
Fn | Can 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), } } } }