Step 1 of 6 · Reading · ~4 min
Learn
Smart Pointers and Memory
Closures: Capture, Fn, FnMut and FnOnce
By the end you can predict which closure trait a closure gets, accept closures in functions and struct fields, and return them.
In Rust Fundamentals you passed closures to map and filter, and in Traits and Generics you wrote bounds like T: Score. This lesson is about what a closure captures, because that decides which bound it satisfies.
What a closure captures
A closure stores the outside variables it uses, or references to them. The compiler picks the weakest way that works:
let name = String::from("ada");
let shout = || name.to_uppercase(); // only reads: borrows &name
println!("{} {}", shout(), shout()); // ADA ADA
println!("{}", name); // ada
let mut total = 0;
let mut add = |x: i32| total += x; // writes: borrows &mut total
add(5);
add(7);
println!("{}", total); // 12
Without mut on let add you get error[E0596]: cannot borrow add as mutable. The &mut borrow lasts until the closure's last call, as in Borrowing:
add(5);
println!("{}", total); // error[E0502]: cannot borrow `total` as immutable
add(7); // closure used again below
move copies or moves, it does not change the trait
move makes the closure own its captures. For a Copy type that means a private copy:
let mut n = 0;
let mut bump = move || { n += 1; n };
println!("{} {}", bump(), bump()); // 1 2
println!("{}", n); // 0: the outside n is untouched
Fn, FnMut, FnOnce
The trait depends only on what the closure's body does with a capture:
| The body... | Implements |
|---|---|
| only reads it | Fn, FnMut, FnOnce |
| mutates it | FnMut, FnOnce |
moves it out (drop(tag), returning it) | FnOnce only |
A move closure that only reads is still Fn. Calling an FnOnce closure uses up the closure:
let tag = String::from("v1");
let finish = || drop(tag);
finish();
finish(); // error[E0382]: use of moved value: `finish`
Closures as parameters and fields
Calling an FnMut needs mut. Ask for the weakest bound you can live with, or callers are refused:
fn repeat<F: FnMut()>(times: u32, mut action: F) {
for _ in 0..times { action(); }
}
fn twice<F: Fn()>(f: F) { f(); f(); }
let mut n = 0;
twice(|| n += 1); // error[E0594]: cannot assign to `n`, as it is a
// captured variable in a `Fn` closure
A struct holds a closure through a generic parameter with the same bound. Call the field with parentheses, because self.action(x) looks for a method named action (E0599):
struct Every<F: FnMut(u32)> { step: u32, count: u32, action: F }
impl<F: FnMut(u32)> Every<F> {
fn tick(&mut self) {
self.count += 1;
if self.count % self.step == 0 {
(self.action)(self.count);
}
}
}
// action: |c| seen.push(c) with step 3, ticked 7 times: seen == [3, 6]
Returning closures, function pointers
Each closure has its own unnamed type, so return one with impl Fn, plus move so it does not borrow a parameter that dies with the call (E0597: k does not live long enough):
fn scaler(k: i32) -> impl Fn(i32) -> i32 { move |x| x * k }
Two closures are two types, so if d { |x| x * 2 } else { |x| x + 100 } is error[E0308]: if and else have incompatible types. Return Box<dyn Fn(i32) -> i32> and Box::new each branch; the mechanics of dyn come in Trait Objects.
A function pointer fn(i32) -> i32 accepts named functions and closures that capture nothing:
fn apply(f: fn(i32) -> i32, x: i32) -> i32 { f(x) }
apply(|x| x + 1, 4); // 5
let k = 3;
apply(|x| x + k, 4); // error[E0308]: mismatched types
Common mistakes
- Expecting
moveto update the outside variable. After thebumpexample the outernstill prints0. - Re-running an impure closure "to be safe". A closure with a hidden counter answers differently on every call, so later answers drift.
- Forgetting
muton a binding or parameter holding anFnMut, or demandingFnfor a closure that mutates. - Writing
self.f(n)for a closure field instead of(self.f)(n).
Your exercise
Finish get in the Memo wrapper so a repeated query is answered from its cache without calling the closure, while calls and hits stay accurate. The closure keeps a private counter, so one extra call changes every later answer.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…