A closure expression produces a closure value with a unique, anonymous type that cannot be written out. A closure type is approximately equivalent to a struct which contains the captured variables. For instance, the following closure:
#![allow(unused)] fn main() { fn f<F : FnOnce() -> String> (g: F) { println!("{}", g()); } let mut s = String::from("foo"); let t = String::from("bar"); f(|| { s += &t; s }); // Prints "foobar". }
generates a closure type roughly like the following:
struct Closure<'a> { s : String, t : &'a String, } impl<'a> FnOnce<()> for Closure<'a> { type Output = String; fn call_once(self) -> String { self.s += &*self.t; self.s } }
so that the call to f
works as if it were:
f(Closure{s: s, t: &t});
The compiler prefers to capture a closed-over variable by immutable borrow, followed by unique immutable borrow (see below), by mutable borrow, and finally by move. It will pick the first choice of these that allows the closure to compile. The choice is made only with regards to the contents of the closure expression; the compiler does not take into account surrounding code, such as the lifetimes of involved variables.
If the move
keyword is used, then all captures are by move or, for Copy
types, by copy, regardless of whether a borrow would work. The move
keyword is usually used to allow the closure to outlive the captured values, such as if the closure is being returned or used to spawn a new thread.
Composite types such as structs, tuples, and enums are always captured entirely, not by individual fields. It may be necessary to borrow into a local variable in order to capture a single field:
#![allow(unused)] fn main() { use std::collections::HashSet; struct SetVec { set: HashSet<u32>, vec: Vec<u32> } impl SetVec { fn populate(&mut self) { let vec = &mut self.vec; self.set.iter().for_each(|&n| { vec.push(n); }) } } }
If, instead, the closure were to use self.vec
directly, then it would attempt to capture self
by mutable reference. But since self.set
is already borrowed to iterate over, the code would not compile.
Captures can occur by a special kind of borrow called a unique immutable borrow, which cannot be used anywhere else in the language and cannot be written out explicitly. It occurs when modifying the referent of a mutable reference, as in the following example:
#![allow(unused)] fn main() { let mut b = false; let x = &mut b; { let mut c = || { *x = true; }; // The following line is an error: // let y = &x; c(); } let z = &x; }
In this case, borrowing x
mutably is not possible, because x
is not mut
. But at the same time, borrowing x
immutably would make the assignment illegal, because a & &mut
reference may not be unique, so it cannot safely be used to modify a value. So a unique immutable borrow is used: it borrows x
immutably, but like a mutable borrow, it must be unique. In the above example, uncommenting the declaration of y
will produce an error because it would violate the uniqueness of the closure's borrow of x
; the declaration of z is valid because the closure's lifetime has expired at the end of the block, releasing the borrow.
Closure types all implement FnOnce
, indicating that they can be called once by consuming ownership of the closure. Additionally, some closures implement more specific call traits:
A closure which does not move out of any captured variables implements FnMut
, indicating that it can be called by mutable reference.
A closure which does not mutate or move out of any captured variables implements Fn
, indicating that it can be called by shared reference.
Note:
move
closures may still implementFn
orFnMut
, even though they capture variables by move. This is because the traits implemented by a closure type are determined by what the closure does with captured values, not how it captures them.
Non-capturing closures are closures that don't capture anything from their environment. They can be coerced to function pointers (e.g., fn()
) with the matching signature.
#![allow(unused)] fn main() { let add = |x, y| x + y; let mut x = add(5,7); type Binop = fn(i32, i32) -> i32; let bo: Binop = add; x = bo(5,7); }
All closure types implement Sized
. Additionally, closure types implement the following traits if allowed to do so by the types of the captures it stores:
The rules for Send
and Sync
match those for normal struct types, while Clone
and Copy
behave as if derived. For Clone
, the order of cloning of the captured variables is left unspecified.
Because captures are often by reference, the following general rules arise:
Sync
if all captured variables are Sync
.Send
if all variables captured by non-unique immutable reference are Sync
, and all values captured by unique immutable or mutable reference, copy, or move are Send
.Clone
or Copy
if it does not capture any values by unique immutable or mutable reference, and if all values it captures by copy or move are Clone
or Copy
, respectively.
© 2010 The Rust Project Developers
Licensed under the Apache License, Version 2.0 or the MIT license, at your option.
https://doc.rust-lang.org/reference/types/closure.html