Wer zum Teufel sind Fn, FnMut und FnOnce? Rust-Closures unter dem Mikroskop

Online Python-Trainer für Anfänger

Lernen Sie Python einfach ohne theoretische Überlastung. Lösen Sie praktische Aufgaben mit automatischer Überprüfung und schreiben Sie Code direkt im Browser.

Kurs starten

Closures in Rust: Magie oder strenge Mathematik?

\n\n

Wenn Sie jemals in Rust so etwas wie |x| x + 1 geschrieben haben, haben Sie Closures verwendet. Aber sobald Sie versuchen, diese Closure an eine Funktion zu übergeben oder in einer Struktur zu speichern, verlangt der Compiler plötzlich: „Welches Trait? Fn, FnMut oder FnOnce?“.

\n\n

Und dann geraten viele ins Stocken. Besonders wenn Sie aus JavaScript, Python oder Ruby kommen, wo Closures „einfach Funktionen sind, die Variablen einfangen“. In Rust ist alles komplizierter (und interessanter).

\n\n

Heute werden wir verstehen, was tatsächlich unter der Haube passiert, wenn Rust entscheidet, welches Trait es Ihrer Closure gibt. Das ist keine Magie – es ist ein strenges Typsystem, das Sie vor Datenrennen und baumelnden Zeigern schützt. Los geht's!

\n\n

Das Wichtigste in Kürze: Die drei Closure-Traits

\n\n

In Rust gibt es drei Traits, die Closures implementieren:

\n\n
    \n
  • FnOnce – kann einmal aufgerufen werden. Übernimmt den Besitz der eingefangenen Variablen.
  • \n
  • FnMut – kann mehrfach aufgerufen werden, kann aber eingefangene Variablen verändern (nimmt &mut).
  • \n
  • Fn – kann mehrfach aufgerufen werden und verändert keine eingefangenen Variablen (nimmt &).
  • \n
\n\n

Jede Closure implementiert automatisch alle passenden Traits. Das heißt, wenn eine Closure Fn implementiert, implementiert sie automatisch auch FnMut und FnOnce. Aber umgekehrt funktioniert es nicht.

\n\n

Warum? Weil Fn das strengste und FnOnce das schwächste ist. Die Vererbungshierarchie sieht so aus:

\n\n
FnOnce  ←  FnMut  ←  Fn
\n\n

Das heißt FnMut: FnOnce und Fn: FnMut. Merken Sie sich diese Leiter – sie ist entscheidend.

\n\n

Wie entscheidet Rust, welches Trait eine Closure bekommt?

\n\n

Der Compiler schaut darauf, wie genau die Closure die eingefangenen Variablen verwendet. Es gibt drei Erfassungsmodi:

\n\n
    \n
  • Per Referenz (&T) – die Closure liest die Variable nur.
  • \n
  • Per veränderlicher Referenz (&mut T) – die Closure verändert die Variable.
  • \n
  • Per Wert (T) – die Closure übernimmt den Besitz der Variable.
  • \n
\n\n

So wirkt sich das auf die Traits aus:

\n\n\n \n \n \n \n
ErfassungsartImplementierte TraitsBeispiel
Nur &TFn + FnMut + FnOnce|| println!("{}", x)
&mut T (aber nicht T)FnMut + FnOnce|| x += 1
T (Besitz)Nur FnOnce|| drop(x) oder move || x
\n\n

Lassen Sie uns jeden Fall mit lebendigem Code durchgehen.

\n\n

Beispiel 1: Nur Lesen → Fn

\n\n
let x = 42;\nlet read_closure = || println!("x = {}", x);\n\n// Kann beliebig oft aufgerufen werden\nread_closure();\nread_closure();\n\n// Und sogar an eine Funktion übergeben werden, die Fn erfordert\nfn call_fn<F: Fn()>(f: F) {\n    f();\n}\ncall_fn(read_closure);
\n\n

Hier liest die Closure einfach x. Sie muss es weder ändern noch übernehmen. Rust versteht: „Okay, das ist sicher – wir geben Fn“.

\n\n

Beispiel 2: Veränderung → FnMut

\n\n
let mut x = 42;\nlet mut mut_closure = || {\n    x += 1;\n    println!("x = {}", x);\n};\n\nmut_closure(); // x = 43\nmut_closure(); // x = 44\n\n// Übergeben an eine Funktion, die FnMut erfordert\nfn call_fn_mut<F: FnMut()>(mut f: F) {\n    f();\n    f();\n}\ncall_fn_mut(mut_closure);
\n\n

Beachten Sie: Wir haben mut_closure als let mut deklariert. Warum? Weil der Aufruf von FnMut eine veränderliche

Blogs

Buchempfehlungen