In Rust, closures are anonymous functions that can "capture" variables from the surrounding scope. The syntax is:
let add = |a: i32, b: i32| a + b;
There are three types of closures in Rust (distinguished by how they capture variables):
&T), can be called multiple times without mutating the environment.&mut T), can modify the closure’s data when called.T).Rust automatically determines the required type, but you can specify it explicitly.
Example of differences:
let s = String::from("hello"); // FnOnce let consume = move || println!("{}", s); // s is no longer accessible after the call
Why can a closure with the keyword move not necessarily be FnOnce, but instead be Fn or FnMut?
Answer: The move keyword transfers variable capture by ownership, but if the data inside the closure is not mutated and not dropped, the closure remains compatible with Fn/FnMut. For example:
let s = String::from("abc"); let c = move || println!("String: {}", s); // c: impl Fn() c();
c can be called multiple times: the value of s is copied into the closure but not destroyed.
Story
A beginner Rust developer in a data streaming project tried to write a closure without move, passing it to another thread. As a result, the compiler complained about outlived borrow, and they had to urgently figure out the intricacies of lifetimes and move.
Story
They introduced mutability inside the closure without changing the type to FnMut, which caused "mismatched types" in all instances of using iterators.
Story
When processing a large array, the closure took ownership of the entire collection by move, unconsciously moving ownership, which resulted in the outer code losing access to the data after the first iteration, leading to attempts to access moved values.