Closures are anonymous functions that can capture variables from the scope where they are defined. They can be called multiple times at once, borrowed immutably, mutably, and can take ownership of captured variables.
In Rust they are implemented using traits:
Fnfor functions that can be called multiple times and borrowed immutably (call(&self, args))FnMutfor functions that can be called multiple times and borrowed mutably (`call_mut(&mut self, arg))FnOncefor functions that can be called only once.call_once(self,args)takes ownership of the closure and consume it
There is a supertrait relationship: if a closure implements Fn, it also implements FnMut and FnOnce since they are implemented in terms of call.
impl Fn and Box<dyn Fn>
The following example returns an impl Fn
pub fn make_quadratic(a: f64, b: f64, c: f64) -> impl Fn(f64) -> f64 {
move |x| a*x*x + b*x + c
}Important
The move keyword before the returned closure signals to the compiler that a, b, and c should be moved into the closure environment.
If we don’t use the move keyword, the closure will immutably borrow the variables a, b, and cwhich is impossible because the closure is returned from the function and will be dropped when the function returns.
As we can see from here the Assembly code would effectively store the three variables in the registry, while at invocation time in reality the following function
pub fn call_make_quadratic(x: f64) -> f64 {
let quad_fn = make_quadratic(5.0, 4.0, 3.0);
quad_fn(x)
}would result in the compiler inlining the assembly. If instead we decide to Box the closure, so that it is allocated on the heap like so:
pub fn make_quadratic_box(a: f64, b: f64, c: f64) -> Box<dyn Fn(f64) -> f64> {
Box::new(make_quadratic(a, b, c))
}we would have a tuple of the closure environment and the vtable pointer for the type:
![[Rust - Box