In Rust zijn closures anonieme functies die variabelen uit de externe scope kunnen "vangen". De syntaxis:
let add = |a: i32, b: i32| a + b;
In Rust zijn er drie soorten closures (onderscheiden op basis van hoe variabelen worden gevangen):
&T), kan meerdere keren worden aangeroepen zonder de omgeving te muteren.&mut T), kan gegevens binnen de closure veranderen bij aanroep.T) overneemt.Rust bepaalt automatisch het benodigde type, maar je kunt het ook expliciet opgeven.
Voorbeeld van de verschillen:
let s = String::from("hello"); // FnOnce let consume = move || println!("{}", s); // s is niet meer beschikbaar na aanroep
Waarom kan een closure met het sleutelwoord move geen FnOnce zijn, maar een Fn of FnMut?
Antwoord: Het sleutelwoord move verplaatst de gevangen variabelen op basis van eigendom, maar als de gegevens binnen de closure niet gemuteerd of vernietigd worden, blijft de closure compatibel met Fn/FnMut. Bijvoorbeeld:
let s = String::from("abc"); let c = move || println!("String: {}", s); // c: impl Fn() c();
c kan meerdere keren worden aangeroepen: de waarde s is gekopieerd in de closure, maar niet vernietigd.
Verhaal
Een beginnende Rust-ontwikkelaar in een datastroomproject probeerde een closure zonder move te schrijven en deze door te geven aan een andere thread. Hierdoor gaf de compiler een foutmelding over outlived borrow, en moest hij snel leren over lifetime en move.
Verhaal
Introduceerde mutability binnen de closure zonder het type te veranderen naar FnMut, wat "mismatched types" veroorzaakte op alle plekken waar iterators werden gebruikt.
Verhaal
Bij het verwerken van een grote array nam de closure met move de hele collectie over, waardoor onbewust het eigendom werd verplaatst, waardoor de buitenste code geen toegang meer had tot de gegevens na de eerste iteratie, wat leidde tot pogingen om naar een moved value te verwijzen.