Closures
Closures are anonymous functions that can capture values from the scope in which they are defined.
Closures are named by assigning them to variables, and have syntax that resemble functions:
fn main() {
// These closures are all equivalent
let add_one = |x| x + 1;
let add_one_2 = |x| { x + 1 };
let add_one_3 = |x: i32| -> i32 { x + 1 };
// Closures that take a single tuple
let take_pair = |(x, y): (i32, i32)| -> i32 { x + y };
let take_pair_2 = |pair: (i32, i32)| -> i32 { pair.0 + pair.1 };
// Closure with multiple parameters
let take_two = |x: i32, y: i32| -> i32 { x + y };
}
Capturing State
Closures can capture state:
fn main() {
let list = vec![1, 2, 3];
println!("Before definition: {list:?}");
// The variable `list` is borrowed by the closure
let closure = || println!("During call: {:?}", list);
println!("Before call: {list:?}");
closure();
println!("After call: {list:?}");
}
Fn Traits
Closures automatically implement up to the least restrictive type of one of the following traits (in order of most to least restrictive) depending on the values they capture:
FnOnce
- The closure might move ownership of captured values.
FnMut
- The closure does not move ownership of captured values.
- The closure might mutate captured values.
Fn
- The closure does not capture any values or captures them immutably without moving ownership.
-
Types that implement
Fn
also implementFnMut
andFnOnce
. -
Types that implement
FnMut
also implementFnOnce
.
FnOnce
-
FnOnce
closures can be called once (all closures can be called). -
Closures that only implement
FnOnce
can be called at most once because they obtain ownership of their values.
fn main() {
// Closures that mutably capture values implement `Fn`
let list = vec![1, 2, 3];
let move_ownership = || drop(list);
move_ownership(); // `list` is dropped here
// move_ownership(); The closure cannot be called again
}
FnMut
-
FnMut
closures can be called multiple times because they do not obtain ownership of any values. -
Closures that only implement
FnMut
(andFnOnce
by extension) cannot be called from multiple threads because they mutate state without synchronization.
fn main() {
let mut list = vec![1, 2, 3];
let mut mutate_list = |n: i32| {
list.push(n); // `list` is mutated here
};
mutate_list(4);
mutate_list(5);
println!("{list:?}");
}
Fn
-
Fn
closures can be called multiple times because they do not obtain ownership of any values. -
Fn
closures can be called across multiple threads because they do not mutate their environment.
fn main() {
// Closures that do not capture values implement `Fn`
let add_one = |x: i32| x + 1;
// Closures that immutably capture values implement `Fn`
let list = vec![1, 2, 3];
let only_borrows = || println!("From closure: {list:?}");
}
Function Pointers
Functions that are assigned to variables become function pointers:
fn add_one(x: usize) -> usize {
x + 1
}
fn main() {
let fn_ptr: fn(usize) -> usize = add_one;
}
Safe function pointers (safe as in safe rust) implement Fn
, FnMut
, and FnOnce
.
Case Study
The Option::unwrap_or_else
method can be called with closures that are Fn
, FnMut
, and FnOnce
.
impl<T> Option<T> {
pub fn unwrap_or_else<F>(self, f: F) -> T
where
F: FnOnce() -> T
{
match self {
Some(x) => x,
None => f(),
}
}
}