Fn, FnMut und FnOnce in Rust
Auf der Seite Closures hast du gelernt, wie du ein Closure (anonyme Funktion) schreibst, wie es Variablen einfängt und wie es sie verändern kann. Diese Seite beantwortet die nächste Frage: Wie schreibst du eine eigene Funktion, die ein Closure annimmt? Und wie gibst du ein Closure aus einer Funktion zurück?
Die Antwort sind drei kleine Traits (Eigenschaften, die ein Typ erfüllt): Fn, FnMut und FnOnce. Stell sie dir wie die Regeln in einer Bibliothek vor. Manche Besucher lesen die Bücher nur (Fn). Manche kritzeln Notizen an den Rand (FnMut). Und manche nehmen ein Buch mit nach Hause und bringen es nie zurück (FnOnce). Rust schaut sich an, was dein Closure tut, und gibt ihm das passende Etikett. Falls die Traits am Anfang einschüchternd wirken: keine Sorge. Es sind nur drei, und der Compiler wählt den richtigen für dich aus.
Warum Closures überhaupt Traits brauchen
Abschnitt betitelt „Warum Closures überhaupt Traits brauchen“Jedes Closure hat seinen eigenen, einzigartigen Typ, den sich der Compiler ausdenkt. Den Namen dieses Typs kannst du nicht hinschreiben. Selbst zwei Closures, die genau gleich aussehen, haben verschiedene Typen:
fn main() { let mut rules = vec![|price: u32| price * 2]; rules.push(|price: u32| price * 2);}error[E0308]: mismatched types --> src/main.rs:3:16 |3 | rules.push(|price: u32| price * 2); | ---- ^^^^^^^^^^^^^^^^^^^^^^ expected closure, found a different closure | = note: no two closures, even if identical, have the same type = help: consider boxing your closure and/or using it as a trait objectDer Compiler sagt: „erwartet wurde ein Closure, gefunden ein anderes Closure“ – keine zwei Closures haben denselben Typ, auch wenn sie identisch sind.
Eine Funktion kann also nicht sagen: „Gib mir ein Closure vom Typ X.“ Stattdessen sagt sie: „Gib mir irgendeinen Typ, den man so aufrufen kann.“ Das ist ein Trait Bound, genau wie auf den Seiten Generics und Traits:
fn price_after<F: Fn(u32) -> u32>(rule: F, price: u32) -> u32 { rule(price)}
fn main() { let coupon = 15; let half_price = |price: u32| price / 2; let use_coupon = |price: u32| price - coupon;
println!("{}", price_after(half_price, 80)); println!("{}", price_after(use_coupon, 80));}Ausgabe:
4065Lies F: Fn(u32) -> u32 so: „F ist irgendein Typ, den du mit einem u32 aufrufen kannst, und er gibt einen u32 zurück.“ Beide Closures passen, obwohl ihre Typen verschieden sind.
Die drei Traits: Fn, FnMut und FnOnce
Abschnitt betitelt „Die drei Traits: Fn, FnMut und FnOnce“Der Compiler schaut sich an, was der Rumpf des Closures mit den eingefangenen Werten macht:
Fn: Es liest sie nur. Du kannst es so oft aufrufen, wie du willst.FnMut: Es verändert sie. Du kannst es oft aufrufen, aber es brauchtmut.FnOnce: Es gibt sie weg (verschiebt sie nach draußen). Du kannst es nur einmal aufrufen, denn danach ist der Wert weg.
fn main() { let shop = String::from("Bäckerei Sonnenschein");
// Fn: liest `shop` nur let greet = || println!("Willkommen in der {shop}!"); greet(); greet();
// FnMut: verändert `orders` let mut orders = 0; let mut take_order = || { orders += 1; println!("Bestellung Nr. {orders} aufgenommen"); }; take_order(); take_order();
// FnOnce: gibt `receipt` an den Aufrufer zurück let receipt = String::from("2 Croissants, 1 Kaffee"); let hand_over = || receipt; let customer_copy = hand_over(); println!("Der Kunde hat: {customer_copy}");}Ausgabe:
Willkommen in der Bäckerei Sonnenschein!Willkommen in der Bäckerei Sonnenschein!Bestellung Nr. 1 aufgenommenBestellung Nr. 2 aufgenommenDer Kunde hat: 2 Croissants, 1 Kaffeehand_over gibt receipt selbst zurück, keine Kopie. Sobald der Kassenbon übergeben ist, hat das Closure nichts mehr zu geben. Deshalb ist es FnOnce.
Dieses Bild solltest du im Kopf behalten. Stell dir drei Kreise vor, einer im anderen:
- Der große äußere Kreis ist
FnOnce. Jedes Closure lässt sich mindestens einmal aufrufen, also liegt jedes Closure hier drin. - Darin liegt ein kleinerer Kreis,
FnMut: Closures, die du immer wieder aufrufen kannst. - Ganz in der Mitte liegt
Fn: Closures, die du immer wieder aufrufen kannst, ohne dass sich etwas ändert.
Jedes Fn-Closure ist also auch ein FnMut und ein FnOnce. Das ist wichtig, wenn du eine Funktion schreibst. Ein Parameter, der FnOnce verlangt, nimmt jedes Closure an. Ein Parameter, der Fn verlangt, ist am wählerischsten.
Eine Sache überrascht viele: Das Schlüsselwort move bestimmt den Trait nicht. move || println!("{shop}") übernimmt das Ownership (Eigentum) an shop, liest es aber nur. Also ist es immer noch Fn. Allein der Rumpf entscheidet.
Funktionen schreiben, die Closures annehmen
Abschnitt betitelt „Funktionen schreiben, die Closures annehmen“Es gibt drei Schreibweisen für denselben Bound. Sie bedeuten alle dasselbe, also nimm die, die sich am besten liest:
// 1. Ein generischer Parameter mit Boundfn apply<F: Fn(i32) -> i32>(f: F, x: i32) -> i32 { f(x)}
// 2. `impl Fn` als Parametertyp: kürzer, kein Name nötigfn apply_twice(f: impl Fn(i32) -> i32, x: i32) -> i32 { f(f(x))}
// 3. Eine `where`-Klausel: übersichtlich, wenn der Bound lang wirdfn apply_to_all<F>(f: F, scores: &[i32]) -> Vec<i32>where F: Fn(i32) -> i32,{ scores.iter().map(|&s| f(s)).collect()}
fn main() { let bonus = 10; let add_bonus = |score: i32| score + bonus;
println!("{}", apply(add_bonus, 50)); println!("{}", apply_twice(add_bonus, 50)); println!("{:?}", apply_to_all(add_bonus, &[40, 65, 90]));}Ausgabe:
6070[50, 75, 100]Vielleicht fragst du dich, wieso wir add_bonus dreimal übergeben konnten. Ein Closure, das seine eingefangenen Werte nur liest, lässt sich billig kopieren. Also kopiert Rust es jedes Mal.
Jetzt zu FnMut und FnOnce. Bei FnMut braucht der Parameter ein mut, weil der Aufruf das Innere des Closures verändert. Ein FnOnce kannst du einmal aufrufen, und das war’s:
fn process_tasks<F: FnMut(&str)>(tasks: &[&str], mut on_done: F) { for task in tasks { on_done(task); }}
fn on_shutdown<F: FnOnce()>(cleanup: F) { println!("fahre herunter..."); cleanup();}
fn main() { let mut finished = Vec::new(); process_tasks(&["E-Mails senden", "Datenbank sichern"], |task| { finished.push(task.to_string()); }); println!("erledigt: {:?}", finished);
on_shutdown(|| { // Diese Schleife übernimmt `finished`, also ist das Closure FnOnce for task in finished { println!("archiviert: {task}"); } });}Ausgabe:
erledigt: ["E-Mails senden", "Datenbank sichern"]fahre herunter...archiviert: E-Mails sendenarchiviert: Datenbank sichernEine gute Faustregel: Verlange den lockersten Trait, mit dem deine Funktion auskommt. Rufst du das Closure nur einmal auf, verlange FnOnce. Dann können Aufrufer alles übergeben. Das siehst du auch in der Standardbibliothek: Option::map nimmt ein FnOnce, Iterator::map dagegen ein FnMut.
Wenn du den falschen Bound wählst
Abschnitt betitelt „Wenn du den falschen Bound wählst“Der Compiler findet jede Unstimmigkeit, und seine Meldungen sind hier sehr hilfreich. Angenommen, eine Funktion verlangt Fn, aber dein Closure erhöht einen Zähler:
fn run_report<F: Fn()>(f: F) { f(); f();}
fn main() { let mut runs = 0; run_report(|| { runs += 1; println!("Bericht Nr. {runs}"); });}error[E0594]: cannot assign to `runs`, as it is a captured variable in a `Fn` closure --> src/main.rs:9:9 |1 | fn run_report<F: Fn()>(f: F) { | - change this to accept `FnMut` instead of `Fn`...8 | run_report(|| { | ---------- -- in this closure | | | expects `Fn` instead of `FnMut`9 | runs += 1; | ^^^^^^^^^ cannot assignDie Meldung heißt: „runs kann nichts zugewiesen werden, weil es eine eingefangene Variable in einem Fn-Closure ist.“ Sie verrät dir sogar die Lösung: Nimm FnMut an und setz mut vor den Parameter. Mit dieser Änderung gibt das Programm Bericht Nr. 1 und Bericht Nr. 2 aus:
fn run_report<F: FnMut()>(mut f: F) { f(); f();}Der andere Klassiker ist, ein FnOnce-Closure zweimal aufzurufen:
fn main() { let report = String::from("Umsatz Q3"); let send = || { let sent = report; println!("sende {sent}"); }; send(); send();}error[E0382]: use of moved value: `send` --> src/main.rs:8:5 |7 | send(); | ------ `send` moved due to this call8 | send(); | ^^^^ value used here after move |note: closure cannot be invoked more than once because it moves the variable `report` out of its environment --> src/main.rs:4:20 |4 | let sent = report; | ^^^^^^Der Hinweis (note) sagt es klar: Das Closure kann nicht mehr als einmal aufgerufen werden, weil es report aus seiner Umgebung herausverschiebt. Der erste Aufruf hat report weggegeben, für einen zweiten bleibt nichts übrig. Brauchst du wirklich zwei Aufrufe, dann leih dir den Wert stattdessen aus (let sent = &report;), und das Closure wird zu Fn. Übergibst du so ein Closure an eine Funktion, die Fn verlangt, bekommst du eine ähnliche Meldung: error[E0507]: cannot move out of report, a captured variable in an Fn closure.
Closures mit impl Fn zurückgeben
Abschnitt betitelt „Closures mit impl Fn zurückgeben“Eine Funktion kann ein Closure auch bauen und zurückgeben. Den Rückgabetyp schreibst du als impl Fn(i32) -> i32. Das bedeutet: „irgendein Closure-Typ; seinen Namen musst du nicht kennen“:
fn make_adder(amount: i32) -> impl Fn(i32) -> i32 { move |x| x + amount}
fn make_multiplier(factor: i32) -> impl Fn(i32) -> i32 { move |x| x * factor}
fn main() { let add_tip = make_adder(5); let triple = make_multiplier(3);
println!("{}", add_tip(20)); println!("{}", triple(7)); println!("{}", add_tip(triple(10)));}Ausgabe:
252135Warum das move? Ohne move würde sich das Closure amount nur ausleihen. Aber amount gehört make_adder und verschwindet in dem Moment, in dem make_adder zurückkehrt. Das Closure hielte dann eine Referenz auf nichts. Rust weigert sich:
error[E0373]: closure may outlive the current function, but it borrows `amount`, which is owned by the current function --> src/main.rs:2:5 |2 | |x| x + amount | ^^^ ------ `amount` is borrowed here | | | may outlive borrowed value `amount` |help: to force the closure to take ownership of `amount` (and any other referenced variables), use the `move` keyword |2 | move |x| x + amount | ++++Der Compiler warnt: Das Closure könnte länger leben als die Funktion, leiht sich aber amount aus, das der Funktion gehört. Und er schlägt gleich move vor. Mit move packt das Closure seine eigene Kopie von amount ein und trägt sie mit sich – so, als würdest du dein Pausenbrot mitnehmen, statt es im Kühlschrank von jemand anderem zu lassen.
Closures in einer Box: viele Closures, ein Typ
Abschnitt betitelt „Closures in einer Box: viele Closures, ein Typ“impl Fn steht für einen versteckten Typ. Du kannst also nicht im if-Zweig ein Closure zurückgeben und im else-Zweig ein anderes. Du kannst auch keine verschiedenen Closures in denselben Vec legen. Für beides steckst du jedes Closure in eine Box: Box<dyn Fn(f64) -> f64> bedeutet „irgendein Closure mit dieser Form, auf dem Heap gespeichert“. Das ist dieselbe Idee wie auf der Seite Trait-Objekte.
Hier ist eine Kasse, die eine Liste benannter Preisregeln der Reihe nach anwendet:
fn shipping_rule(express: bool, fee: f64) -> Box<dyn Fn(f64) -> f64> { if express { Box::new(move |total| total + fee * 2.0) } else { Box::new(move |total| total + fee) }}
fn main() { let member_percent = 10.0; let coupon = 5.0;
let rules: Vec<(&str, Box<dyn Fn(f64) -> f64>)> = vec![ ("Mitgliederrabatt", Box::new(move |total: f64| total * (1.0 - member_percent / 100.0))), ("Gutschein", Box::new(move |total: f64| total - coupon)), ("Standardversand", shipping_rule(false, 3.5)), ];
let mut total = 80.0; println!("Start: {total:.2}"); for (name, rule) in &rules { total = rule(total); println!("nach {name}: {total:.2}"); }}Ausgabe:
Start: 80.00nach Mitgliederrabatt: 72.00nach Gutschein: 67.00nach Standardversand: 70.50Die Box kostet eine winzige Speicherreservierung auf dem Heap, und jeder Aufruf läuft über einen Zeiger. Das ist fast nie ein Problem. Nimm impl Fn, wenn es einen einzigen Closure-Typ gibt, und Box<dyn Fn>, wenn es viele sind.
Ein Closure in einem Struct speichern
Abschnitt betitelt „Ein Closure in einem Struct speichern“Auch ein Struct kann ein Closure enthalten. Da du den Typ des Closures nicht benennen kannst, machst du das Struct darüber generisch. Hier ist ein kleiner Button, der beim Klicken Code ausführt:
struct Button<F: Fn()> { label: String, on_click: F,}
impl<F: Fn()> Button<F> { fn click(&self) { println!("[{}] geklickt", self.label); (self.on_click)(); }}
fn main() { let user = String::from("Ada"); let save = Button { label: String::from("Speichern"), on_click: move || println!("speichere den Entwurf von {user}"), };
save.click(); save.click();}Ausgabe:
[Speichern] geklicktspeichere den Entwurf von Ada[Speichern] geklicktspeichere den Entwurf von AdaAchte auf die Klammern in (self.on_click)(). Sie sagen Rust: „Nimm das Feld und ruf es dann auf.“ Soll der Button bei jedem Klick seine eingefangenen Werte verändern, nimm F: FnMut() und lass die Methode &mut self annehmen.
Funktionszeiger: fn(i32) -> i32
Abschnitt betitelt „Funktionszeiger: fn(i32) -> i32“Auch normale Funktionen kannst du weitergeben. Ihr Typ wird mit einem kleinen fn geschrieben, etwa fn(i32) -> i32. Das ist ein Funktionszeiger (Function Pointer), also einfach die Adresse von etwas Code. Ein Closure, das nichts einfängt, kann sich ebenfalls in einen verwandeln:
fn double(x: i32) -> i32 { x * 2}
fn square(x: i32) -> i32 { x * x}
fn apply_op(op: fn(i32) -> i32, x: i32) -> i32 { op(x)}
fn main() { println!("{}", apply_op(double, 4)); println!("{}", apply_op(|x| x - 1, 4));
let steps: [fn(i32) -> i32; 3] = [double, square, |x| x + 1]; let mut value = 3; for step in steps { value = step(value); } println!("{value}");}Ausgabe:
8337Alle Funktionszeiger mit derselben Signatur teilen sich einen Typ. Deshalb kannst du sie ohne Box in ein Array legen. Der Haken: Ein Closure, das etwas einfängt, kann kein Funktionszeiger werden. Der Compiler sagt dann closures can only be coerced to fn types if they do not capture any variables – Closures lassen sich nur dann in fn-Typen umwandeln, wenn sie keine Variablen einfangen. Die gute Nachricht gilt in die andere Richtung: Jede normale Funktion implementiert auch Fn, FnMut und FnOnce. Du kannst double also an das apply von oben übergeben. Deshalb verwendet der meiste Code die Closure-Traits: Sie akzeptieren beides.
Häufige Fehler
Abschnitt betitelt „Häufige Fehler“mut beim FnMut-Parameter vergessen. Ein Aufruf verändert ein FnMut, deshalb muss seine Variable mut sein:
error[E0596]: cannot borrow `on_done` as mutable, as it is not declared as mutable --> src/main.rs:3:9 |3 | on_done(task); | ^^^^^^^ cannot borrow as mutable |help: consider changing this to be mutable |1 | fn process_tasks<F: FnMut(&str)>(tasks: &[&str], mut on_done: F) { | +++Übersetzt: on_done kann nicht veränderbar ausgeliehen werden, weil es nicht als veränderbar deklariert ist. Der Vorschlag darunter zeigt dir genau, wo das mut hingehört.
Verschiedene Closures aus -> impl Fn zurückgeben. Zwei Closures sind zwei Typen, auch wenn sie gleich aussehen:
fn shipping_rule(express: bool, fee: f64) -> impl Fn(f64) -> f64 { if express { move |total| total + fee * 2.0 } else { move |total| total + fee }}error[E0308]: `if` and `else` have incompatible types --> src/main.rs:5:9 |2 | / if express {3 | | move |total| total + fee * 2.0 | | ------------------------------ | | | | | the expected closure | | expected because of this4 | | } else {5 | | move |total| total + fee | | ^^^^^^^^^^^^^^^^^^^^^^^^ expected closure, found a different closure6 | | } | |_____- `if` and `else` have incompatible types | = note: expected closure `{closure@src/main.rs:3:9: 3:21}` found closure `{closure@src/main.rs:5:9: 5:21}` = note: no two closures, even if identical, have the same type = help: consider boxing your closure and/or using it as a trait objecthelp: you could change the return type to be a boxed trait object |1 - fn shipping_rule(express: bool, fee: f64) -> impl Fn(f64) -> f64 {1 + fn shipping_rule(express: bool, fee: f64) -> Box<dyn Fn(f64) -> f64> { |help: if you change the return type to expect trait objects, box the returned expressions |3 ~ Box::new(move |total| total + fee * 2.0)4 | } else {5 ~ Box::new(move |total| total + fee) |if und else haben hier unvereinbare Typen. Mach, was der Compiler vorschlägt: Gib Box<dyn Fn(f64) -> f64> zurück und verpack jedes Closure in Box::new(...).
Ein Closure-Feld wie eine Methode aufrufen. Wenn du self.on_click() schreibst, sucht Rust nach einer Methode namens on_click:
error[E0599]: no method named `on_click` found for reference `&Button<F>` in the current scope --> src/main.rs:9:14 |9 | self.on_click(); | ^^^^^^^^ field, not a method |help: to call the type parameter stored in `on_click`, surround the field access with parentheses |9 | (self.on_click)(); | + +Die Meldung sagt: Das ist ein Feld, keine Methode. Setz den Feldzugriff in Klammern, dann klappt der Aufruf.
Übungen
Abschnitt betitelt „Übungen“1. Ein Ticketautomat
Abschnitt betitelt „1. Ein Ticketautomat“Schreib eine Funktion ticket_dispenser(start: u32), die ein Closure zurückgibt. Jeder Aufruf des Closures liefert die nächste Ticketnummer: erst start, dann start + 1 und so weiter. Bau zwei Automaten (eine Bäckerei, die bei 100 beginnt, und eine Apotheke, die bei 1 beginnt) und zeig, dass sie getrennt zählen. Tipp: Das Closure verändert seinen eigenen Zähler. Der Rückgabetyp ist also impl FnMut() -> u32, und du brauchst move.
Lösung anzeigen
fn ticket_dispenser(start: u32) -> impl FnMut() -> u32 { let mut next = start; move || { let ticket = next; next += 1; ticket }}
fn main() { let mut bakery = ticket_dispenser(100); let mut pharmacy = ticket_dispenser(1);
println!("Bäckerei: {}", bakery()); println!("Bäckerei: {}", bakery()); println!("Apotheke: {}", pharmacy()); println!("Bäckerei: {}", bakery());}Ausgabe:
Bäckerei: 100Bäckerei: 101Apotheke: 1Bäckerei: 102Jeder Automat hat sein eigenes next per move eingepackt, deshalb teilen sie sich nie einen Zähler. Die Variablen bakery und pharmacy brauchen mut, weil sie FnMut sind.
2. Eine Hilfsfunktion für Wiederholungsversuche
Abschnitt betitelt „2. Eine Hilfsfunktion für Wiederholungsversuche“Schreib retry(max_attempts, task). Die Funktion ruft task so lange auf, bis es Ok zurückgibt oder bis sie es max_attempts-mal versucht hat. Gib für jeden fehlgeschlagenen Versuch eine Zeile aus. Teste sie mit einem Closure, das zweimal mit "Server ausgelastet" scheitert und dann klappt. Tipp: Das Test-Closure zählt seine eigenen Aufrufe. Der Bound ist also FnMut() -> Result<String, String>, und der Parameter braucht mut.
Lösung anzeigen
fn retry<F>(max_attempts: u32, mut task: F) -> Result<String, String>where F: FnMut() -> Result<String, String>,{ for attempt in 1..=max_attempts { match task() { Ok(value) => return Ok(value), Err(e) => println!("Versuch {attempt} fehlgeschlagen: {e}"), } } Err(String::from("aufgegeben"))}
fn main() { let mut calls = 0; let result = retry(5, || { calls += 1; if calls < 3 { Err(String::from("Server ausgelastet")) } else { Ok(String::from("Daten heruntergeladen")) } }); println!("{:?} nach {calls} Aufrufen", result);}Ausgabe:
Versuch 1 fehlgeschlagen: Server ausgelastetVersuch 2 fehlgeschlagen: Server ausgelastetOk("Daten heruntergeladen") nach 3 Aufrufenretry ruft die Aufgabe mehr als einmal auf, also reicht FnOnce nicht. Und die Aufgabe verändert calls, also reicht auch Fn nicht. FnMut passt genau. Sobald retry zurückkehrt, ist das Closure fertig, und main kann calls wieder lesen.
Zusammenfassung
Abschnitt betitelt „Zusammenfassung“- Jedes Closure hat seinen eigenen, einzigartigen Typ. Deshalb nehmen Funktionen Closures über einen Trait Bound wie
F: Fn(i32) -> i32an. Fnliest seine eingefangenen Werte nur,FnMutverändert sie, undFnOnceverschiebt sie nach draußen und lässt sich nur einmal aufrufen. Der Rumpf entscheidet, nichtmove.- Jedes
Fnist auchFnMutundFnOnce. Verlange den lockersten Trait, den deine Funktion braucht. - Schreib Bounds als
<F: Fn(..)>, alsimpl Fn(..)oder in einerwhere-Klausel.FnMut-Parameter brauchenmut. - Gib ein einzelnes Closure mit
-> impl Fn(..)zurück (meist mitmove). Für verschiedene Closures aus mehreren Zweigen oder in einemVecnimmBox<dyn Fn(..)>. fn(i32) -> i32ist ein Funktionszeiger. Normale Funktionen und Closures, die nichts einfangen, passen hinein.
Kurzer Check
Abschnitt betitelt „Kurzer Check“Kurzer Check
1. Ein Closure führt total += price auf einer eingefangenen Variablen aus. Welches ist der strengste Trait, den es implementiert?
2. Deine Funktion ruft ihren Closure-Parameter genau einmal auf. Welcher Bound akzeptiert die meisten Closures?
3. Warum braucht make_adder(amount: i32) -> impl Fn(i32) -> i32 ein move |x| x + amount?
4. Du willst einen Vec mit mehreren verschiedenen Rabatt-Closures. Welchen Typ brauchen die Elemente?
Punkte: 0 / 4
Weiterlesen
Abschnitt betitelt „Weiterlesen“- Verschieben erfasster Werte aus Closures – Das Rust-Buch: die offizielle Erklärung, wie der Compiler zwischen
Fn,FnMutundFnOncewählt. Fn,FnMutundFnOnce– Doku der Standardbibliothek (auf Englisch): die Definition jedes Traits, mit kurzen Beispielen, wann du ihn verwendest.- Input parameters – Rust by Example (auf Englisch): ausführbare Beispiele für Funktionen, die Closures mit jedem der drei Bounds annehmen.
- Output parameters – Rust by Example (auf Englisch): Closures mit
impl Fnzurückgeben, und warum dabeimovenötig ist. - Closure types – The Rust Reference (auf Englisch): die genauen Regeln, wie Closures Werte einfangen und welche Traits sie implementieren.