Skip to content

← Closures and Iterators step 3 of 28

Medium Primitives

move: when the closure must own its captures

Build one validator closure per limit, then run a fixed set of probes past all of them.

pub fn make_validators(limits: Vec<i64>) -> Vec<i64>

For each limit, the validator answers x <= limit. The probe set is fixed and given to you:

const PROBES: [i64; 6] = [-10, 0, 1, 5, 10, 100];

Return, for each probe in that order, how many validators accept it. With limits = [10, 5, 100] the answer is [3, 3, 3, 3, 2, 1]: every validator accepts -10, 0, 1 and 5; only the 10 and 100 validators accept 10; only the 100 validator accepts 100. An empty limits gives six zeroes.

The starter is one word away from correct

Compile it. You get exactly one error, and it is the error that sends more beginners to Stack Overflow than any other closure diagnostic:

error[E0373]: closure may outlive the current function, but it borrows
              `limit`, which is owned by the current function

Read it literally, because it is telling the exact truth. limit is a local — it is the parameter of the map closure, and it dies when that closure returns. The inner closure only borrows it. But you are putting that inner closure into a Box<dyn Fn(i64) -> bool>, and a bare dyn Trait in a Box means dyn Trait + 'static: the compiler has been told this value may live forever. A thing that lives forever cannot hold a reference to a thing that dies at the end of the line. Rejected.

The fix is move.

What move actually does

move changes one thing: it forces every capture to be taken by value instead of by reference. That is the entire semantics. It is a property of how the closure gets its data, not of the closure’s type, not of when it runs, not of how many times it can be called.

Without move, the compiler infers the least restrictive capture mode that makes the body compile — usually a shared borrow. With move, Copy types are copied in and non-Copy types are moved in, and the closure owns whatever it holds from then on.

This is why thread::spawn(move || ...) is written the way it is. Without the move you get E0373 about the captured variable; the closure would borrow from a stack frame that may vanish while the thread is still running. move is not thread magic. It is “give the closure its own copy”, and threads are just the most common place you need it.

move does not make a closure FnOnce

Say it out loud, because half the internet gets this wrong. From item 9.2: the trait a closure implements is decided by what its body does to the captures, never by move.

let n = 5i64;
let f = move |x: i64| x + n;
println!("{} {} {}", f(1), f(2), f(3));   // fine — `f` is `Fn`

Here the validators are all move and all Fn, which is why they can sit in a Vec and be called once per probe — six times each.

A move closure can itself be Copy

This is small, exact, and frequently useful. A closure’s captured state is its fields, so the closure struct derives its own auto traits from them:

let n = 5i64;
let f = move |x: i64| x + n;
let g = f;
println!("{} {}", g(1), f(2));   // fine — `i64: Copy`, so `f: Copy`
let s = String::from("hi");
let h = move |x: i64| format!("{s}{x}");
let k = h;
// h(2);   // E0382: use of moved value — `String` is not `Copy`

Same move, opposite outcome, and the only difference is what got captured. The same reasoning decides whether your closure is Send and Sync: it is if all its captures are. There is no closure-specific rule to learn — it is the ordinary struct rule applied to a struct you did not write.

The loop-variable trap this problem is really about

The bug in the starter has a nastier cousin. If you build closures in a for loop without move, each one borrows the loop variable, and the borrow ends when the iteration ends — so the error appears at the closing brace of the loop, or worse, at the return, several lines away from the closure you actually wrote wrong. When a closure error points somewhere that makes no sense, ask first “which local did this borrow, and when does that local die?”

Grade is compile + tests + clippy -D warnings.

Loading visualization…