Anonymous functions that can capture their environment:
- by reference
&T
- by mutable reference
&mut T
- by value
T
The preference goes from top-to-bottom — if possible borrow by immutable or mutable reference, and only capture by value when really needed.
fn main() {
let var = 4; // var declared here
let add_var = |x| x + var; // closure captures var
println!("{} + 3 = {}", var, add_var(3));
}
If the closure only contains a single expression, the block brackets {}
can be
left out. Closures can be type-annotated as regular functions, but types are
inferrable:
fn main() {
let squared = |x: i32| -> i32 { x * x }; // full syntax
let squared = |x: i32| -> i32 x * x; // no block
let squared = |x| { x * x }; // inferred type (more flexible)
let squared = |x| x * x; // inferred type and no block
}
When only immutable values are captured, and they're not consumed, they're borrowed by reference:
fn main() {
let var = 4;
let add_var = |x| x + var; // var is &var here
println!("{} + 3 = {}", var, add_var(3));
}
Mutable values are captured as &mut T
and the closure needs to be stored
as mut
as well:
fn main() {
let mut count = 0;
let mut inc_print = || { // is mut because it mutates the environment
count += 1; // count is &mut count here
println!("count is {}", count);
};
inc_print();
inc_print();
}
Values that do not implement the Copy
trait and are consumed in the closure
get their ownership moved:
fn main() {
let a = "hello".to_string(); // String does not implement Copy
let fs = || {
let b = a; // a moved into b
println!("we got {}", b);
};
// println!("a is {}", a); // nope 🙀 a is moved
}
If a
was i32
, this code would work, because i32
implements Copy
, and a
would get captured as &a
. The b
variable would get a copy of the value.
The move
keyword transforms captured variabled by reference or mutable
reference to owned by value:
use std::ops::Add;
fn make_adder<T: Add<Output=T> + Copy>(x: T) -> impl Fn(T) -> T {
move |y| x + y
}
fn main() {
let add_5 = make_adder(5);
println!("5 + 10 = {}", add_5(10));
}
There are three traits in the Fn
family:
Fn
with thecall
method that takes&self
FnMut
(supertrait ofFn
) with thecall_mut
method that takes&mut self
FnOnce
(supertrait ofFnMut
) with thecall_once
method that takesself
Closures that capture by reference implement the Fn
trait. Closures that
capture by mutable reference implement FnMut
and allow mutating the
environment. Closures capturing by value implement FnOnce
, because once
they're called, the values are moved, and cannot be called again.
Function pointers implement the Fn
trait and can be used as such:
fn apply_twice<T>(f: impl Fn(T) -> T, a: T) -> T {
f(f(a))
}
fn times_two(x: i32) -> i32 {
x * 2
}
fn main() {
println!("function: {}", apply_twice(times_two, 3));
println!("closure: {}", apply_twice(|x| x * 2, 5));
}
Closures are actually implemented as structs created at compile-time. The
captured environment becomes the struct's fields. The created struct implements
the proper Fn
trait.
Implementing a pseudo-closure struct implementing the Fn
trait would look
something like this:
// captured environment
struct Adder<'a> {
x: &'a i32,
}
// quasi Fn impl
impl<'a> Adder<'a> {
fn call(&self, y: i32) -> i32 {
self.x + y
}
}
fn main() {
let x = 5;
let f = Adder { x: &x }; // capture the environment
println!("{} + 3 = {}", x, f.call(3)); // would f(3) with a regular closure
}
A similar example for FnMut
also shows why closures that mutate the
environment must be marked as mut
— the environment is a mutable struct
:
// captured mutable environment
struct Counter<'a> {
count: &'a mut i32,
}
// quasi FnMut impl
impl<'a> Counter<'a> {
fn call_mut(&mut self) {
*self.count += 1;
println!("count is {}", self.count)
}
}
fn main() {
let mut count = 0;
let mut counter = Counter {
count: &mut count, // capture the mutable environment
};
counter.call_mut(); // would counter() with a regular closure
counter.call_mut(); // would counter() with a regular closure
}
Finally, showing how the FnOnce
trait consumes the environment:
// captured owned environment
struct Spawner {
s: String,
}
// quasi FnOnce impl
impl Spawner {
fn call_once(self) {
let a = self.s;
println!("spawn a thread or something with {}", a);
}
}
fn main() {
let s = "yolo".to_string();
let spawn = Spawner { s };
spawn.call_once(); // would spawn() with a regular closure
// spawn.call_once(); // nope 🙀 spawner is moved
// println!("s is {}", s); // nope 🙀 s is moved
}