Rust Closures verstehen: Ein praktischer Blick auf Fn, FnMut und FnOnce
Emily Parker
Product Engineer · Leapcell

In der Programmiersprache Rust sind Closures eine leistungsstarke und flexible Funktion, mit der Sie anonyme Funktionen definieren und Variablen aus ihrer Umgebung erfassen können. Das Closure-System von Rust wird durch drei Kern-Traits definiert – Fn, FnMut und FnOnce – die bestimmen, wie Closures mit erfassten Variablen interagieren, wie oft sie aufgerufen werden können und ob sie ihre Umgebung ändern können. Das Verständnis dieser Traits ist entscheidend, um den Closure-Mechanismus von Rust zu beherrschen und effizienten, sicheren Code zu schreiben.
Dieser Artikel bietet eine detaillierte Einführung in die drei Traits – Fn, FnMut und FnOnce – einschließlich ihrer Definitionen, Anwendungsfälle, Methoden, anwendbaren Szenarien und Best Practices, zusammen mit Codebeispielen, die Ihnen helfen, die relevanten Konzepte umfassend zu erlernen.
Was sind Fn, FnMut und FnOnce?
Fn, FnMut und FnOnce sind drei Traits, die in der Standardbibliothek von Rust definiert sind, um das Verhalten von Closures (oder allen aufrufbaren Objekten) zu beschreiben. Der Hauptunterschied besteht darin, wie sie auf erfasste Variablen zugreifen und welche Eigentumsregeln gelten, wenn sie aufgerufen werden:
- FnOnce: Gibt an, dass ein Closure einmal aufgerufen werden kann. Nach dem Aufruf wird der Closure selbst verbraucht und kann nicht mehr verwendet werden.
- FnMut: Gibt an, dass ein Closure mehrmals aufgerufen werden kann und beim Aufruf erfasste Variablen ändern kann.
- Fn: Gibt an, dass ein Closure mehrmals aufgerufen werden kann und nur erfasste Variablen liest, ohne sie zu ändern.
Es besteht eine Vererbungsbeziehung zwischen diesen drei Traits:
- Fn erbt von FnMut und FnMut erbt von FnOnce.
- Wenn also ein Closure Fn implementiert, implementiert er auch automatisch FnMut und FnOnce; wenn er FnMut implementiert, implementiert er auch FnOnce.
Definition der einzelnen Traits
FnOnce
Der FnOnce
-Trait definiert eine call_once
-Methode mit der folgenden Signatur:
pub trait FnOnce<Args> { type Output; fn call_once(self, args: Args) -> Self::Output; }
- Eigenschaften:
call_once
nimmtself
(anstatt&self
oder&mut self
), was bedeutet, dass beim Aufruf des Closures das Eigentum an sich selbst übertragen wird und er daher nur einmal aufgerufen werden kann. - Anwendungsfälle: Geeignet für Szenarien, in denen ein Closure erfasste Variablen verschieben oder eine einmalige Operation ausführen muss.
FnMut
Der FnMut
-Trait definiert eine call_mut
-Methode mit der folgenden Signatur:
pub trait FnMut<Args>: FnOnce<Args> { fn call_mut(&mut self, args: Args) -> Self::Output; }
- Eigenschaften:
call_mut
nimmt&mut self
, wodurch der Closure beim Aufruf seinen internen Zustand oder erfasste Variablen ändern kann, und er kann mehrmals aufgerufen werden. - Anwendungsfälle: Geeignet für Szenarien, in denen ein Closure seine Umgebung über mehrere Aufrufe hinweg ändern muss.
Fn
Der Fn
-Trait definiert eine call
-Methode mit der folgenden Signatur:
pub trait Fn<Args>: FnMut<Args> { fn call(&self, args: Args) -> Self::Output; }
- Eigenschaften:
call
nimmt&self
, was bedeutet, dass der Closure sich selbst und die erfassten Variablen nur unveränderlich ausleiht. Er kann mehrmals aufgerufen werden, ohne die Umgebung zu ändern. - Anwendungsfälle: Geeignet für Szenarien, in denen ein Closure mehrmals aufgerufen werden muss und nur Daten liest.
Wie Closures diese Traits implementieren
Der Compiler von Rust bestimmt automatisch, welche Traits ein Closure implementiert, basierend darauf, wie er erfasste Variablen verwendet. Ein Closure kann Variablen auf drei Arten erfassen:
- Nach Wert (move): Der Closure übernimmt das Eigentum an der Variable.
- Nach veränderlicher Referenz (
&mut
): Der Closure erfasst eine veränderliche Referenz auf die Variable. - Nach unveränderlicher Referenz (
&
): Der Closure erfasst eine unveränderliche Referenz auf die Variable.
Der implementierte Trait hängt davon ab, wie die erfasste Variable verwendet wird:
- Implementiert nur FnOnce: Der Closure verschiebt die erfasste Variable.
- Implementiert FnMut und FnOnce: Der Closure ändert die erfasste Variable.
- Implementiert Fn, FnMut und FnOnce: Der Closure liest nur die erfasste Variable.
Codebeispiele
Closure, die FnOnce implementiert
fn main() { let s = String::from("hello"); let closure = move || { drop(s); // Verschiebt s und verwirft es }; closure(); // Einmal aufgerufen // closure(); // Fehler: Closure wurde verbraucht }
Erläuterung: Der Closure erfasst s
durch Verschieben, übernimmt das Eigentum daran und verwirft es beim Aufruf. Da s
verschoben wird, kann der Closure nur einmal aufgerufen werden, wodurch er nur FnOnce
implementiert.
Closure, die FnMut implementiert
fn main() { let mut s = String::from("hello"); let mut closure = || { s.push_str(" world"); // Ändert s }; closure(); // Erster Aufruf closure(); // Zweiter Aufruf println!("{}", s); // Gibt "hello world world" aus }
Erläuterung: Der Closure erfasst s
durch veränderliche Referenz und ändert sie bei jedem Aufruf. Da er seine Umgebung ändern muss, implementiert er FnMut
und FnOnce
.
Closure, die Fn implementiert
fn main() { let s = String::from("hello"); let closure = || { println!("{}", s); // Liest s ohne Änderung }; closure(); // Erster Aufruf closure(); // Zweiter Aufruf }
Erläuterung: Der Closure erfasst s
durch unveränderliche Referenz und liest sie nur ohne Änderung. Daher implementiert er Fn
, FnMut
und FnOnce
.
Verwenden dieser Traits in Funktionsparametern
Closures können als Argumente an Funktionen übergeben werden, und Funktionen müssen das erforderliche Closure-Verhalten mithilfe von Trait-Grenzen angeben.
Verwenden von FnOnce
fn call_once<F>(f: F) where F: FnOnce(), { f(); } fn main() { let s = String::from("hello"); call_once(move || { drop(s); }); }
Erläuterung: call_once
akzeptiert einen FnOnce
-Closure und ruft ihn einmal auf, wodurch er für Closures geeignet ist, die erfasste Variablen verschieben.
Verwenden von FnMut
fn call_mut<F>(mut f: F) where F: FnMut(), { f(); f(); } fn main() { let mut s = String::from("hello"); call_mut(|| { s.push_str(" world"); }); println!("{}", s); // Gibt "hello world world" aus }
Erläuterung: call_mut
akzeptiert einen FnMut
-Closure und ruft ihn zweimal auf. Der Closure kann die erfasste Variable ändern. Beachten Sie, dass f
als mut
deklariert werden muss.
Verwenden von Fn
fn call_fn<F>(f: F) where F: Fn(), { f(); f(); } fn main() { let s = String::from("hello"); call_fn(|| { println!("{}", s); }); }
Erläuterung: call_fn
akzeptiert einen Fn
-Closure und ruft ihn zweimal auf. Der Closure liest nur die erfasste Variable.
Wann sollte man welchen Trait verwenden?
Die Wahl des richtigen Traits hängt vom für den Closure erforderlichen Verhalten ab:
FnOnce
- Anwendungsfall: Der Closure wird nur einmal aufgerufen oder muss erfasste Variablen verschieben.
- Beispiel: Eine einmalige Operation, die das Eigentum überträgt.
FnMut
- Anwendungsfall: Der Closure muss mehrmals aufgerufen werden und erfasste Variablen ändern.
- Beispiel: Ein Zähler oder eine Zustandsaktualisierung.
Fn
- Anwendungsfall: Der Closure muss mehrmals aufgerufen werden und nur erfasste Variablen lesen.
- Beispiel: Protokollierung oder Datenabfragen.
Bei der Entwicklung von Funktionen erhöht die Auswahl des permissivsten Traits die Flexibilität. Beispielsweise akzeptiert FnOnce
alle Closures, schränkt aber den Aufruf ein, während Fn
mehrere Aufrufe ermöglicht, aber Unveränderlichkeit erfordert.
Best Practices
- Fn bevorzugen: Wenn ein Closure keine Variablen ändern muss, verwenden Sie
Fn
für maximale Kompatibilität. - FnMut verwenden, wenn eine Änderung erforderlich ist: Wählen Sie
FnMut
, wenn ein Closure den Zustand aktualisieren muss. - FnOnce für einmalige Closures verwenden: Wenn ein Closure Variablen verschiebt oder eine einmalige Aufgabe ausführt, verwenden Sie
FnOnce
. - Den richtigen Trait für APIs wählen: Verwenden Sie
FnOnce
für einmalige Aufrufe,Fn
für mehrere schreibgeschützte Aufrufe undFnMut
für mehrere Aufrufe mit Änderungen. - Auf Lebensdauern achten: Stellen Sie sicher, dass erfasste Variablen lange genug leben, um Ausleihfehler zu vermeiden.
Wir sind Leapcell, Ihre erste Wahl für das Hosten von Rust-Projekten.
Leapcell ist die Serverlose Plattform der nächsten Generation für Webhosting, asynchrone Aufgaben und Redis:
Mehrsprachige Unterstützung
- Entwickeln Sie mit Node.js, Python, Go oder Rust.
Stellen Sie unbegrenzt Projekte kostenlos bereit
- Sie zahlen nur für die Nutzung – keine Anfragen, keine Gebühren.
Unschlagbare Kosteneffizienz
- Pay-as-you-go ohne Leerlaufgebühren.
- Beispiel: 25 US-Dollar unterstützen 6,94 Millionen Anfragen bei einer durchschnittlichen Antwortzeit von 60 ms.
Optimierte Entwicklererfahrung
- Intuitive Benutzeroberfläche für mühelose Einrichtung.
- Vollautomatische CI/CD-Pipelines und GitOps-Integration.
- Echtzeitmetriken und -protokollierung für verwertbare Erkenntnisse.
Mühelose Skalierbarkeit und hohe Leistung
- Automatische Skalierung zur einfachen Bewältigung hoher Parallelität.
- Kein Betriebsaufwand – konzentrieren Sie sich einfach auf das Bauen.
Erfahren Sie mehr in der Dokumentation!
Folgen Sie uns auf X: @LeapcellHQ