Closures
Rust’s Function Traits
trait FnOnce<Args>trait FnMut<Args>: FnOnce<Args>trait Fn<Args>: FnMut<Args>
Note:
- Instances of FnOnce can only be called once.
- Instances of FnMut can be called repeatedly and may mutate state.
- Instances of Fn can be called repeatedly without mutating state.
Fn(a trait) andfn(a function pointer) are different!
These traits are implemented by:
- Function Pointers
- Closures
Function Pointers
fn add_one(x: usize) -> usize {
x + 1
}
fn main() {
let ptr: fn(usize) -> usize = add_one;
println!("ptr(5) = {}", ptr(5));
}
Closures
- Defined with
|<args>| - Most basic kind, are just function pointers
fn main() {
let clos: fn(usize) -> usize = |x| x + 5;
println!("clos(5) = {}", clos(5));
}
Capturing
- Closures can capture their environment.
- Now it’s an anonymous
struct, not afn - It implements
Fn
fn main() {
let increase_by = 1;
let clos = |x| x + increase_by;
println!("clos(5) = {}", clos(5));
}
The variable increase_by that is captured by the closure here is called an upvar
or a free variable.
Capturing Mutably
- Closures can capture their environment by mutable reference
- Now it implements
FnMut
fn main() {
let mut total = 0;
let mut update = |x| total += x;
update(5);
update(5);
println!("total: {}", total);
}
Note:
The closure is dropped before the println!, making total accessible again (the &mut ref stored in the closure is now gone).
If you try and call update() after the println! you get a compile error.
Capturing by transferring ownership
This closure implements FnOnce.
fn main() {
let items = vec![1, 2, 3, 4];
let update = move || {
for item in items {
println!("item is {}", item);
}
};
update();
// println!("items is {:?}", items);
}
But why?
- But why is this useful?
- It makes iterators really powerful!
fn main() {
let items = [1, 2, 3, 4, 5, 6];
let n = 2;
for even_number in items.iter().filter(|x| (**x % n) == 0) {
println!("{} is even", even_number);
}
}
Cleaning up
It’s also very powerful if you have something you need to clean up.
- You do some set-up
- You want do some work (defined by the caller)
- You want to clean up after.
#![allow(unused)]
fn main() {
fn setup_teardown<F, T>(f: F) -> T where F: FnOnce(&mut Vec<u32>) -> T {
let mut state = Vec::new();
println!("> Setting up state");
let t = f(&mut state);
println!("< State contains {:?}", state);
t
}
}
Cleaning up
fn setup_teardown<F, T>(f: F) -> T where F: FnOnce(&mut Vec<u32>) -> T {
let mut state = Vec::new();
println!("> Setting up state");
let t = f(&mut state);
println!("< State contains {:?}", state);
t
}
fn main() {
setup_teardown(|s| s.push(1));
setup_teardown(|s| {
s.push(1);
s.push(2);
s.push(3);
});
}
Note:
In release mode, all this code just gets inlined.