Closures in Rust: Magie oder strenge Mathematik?
\n\nWenn 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?“.
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\nHeute 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\nDas Wichtigste in Kürze: Die drei Closure-Traits
\n\nIn 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
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.
Warum? Weil Fn das strengste und FnOnce das schwächste ist. Die Vererbungshierarchie sieht so aus:
FnOnce ← FnMut ← Fn\n\nDas heißt FnMut: FnOnce und Fn: FnMut. Merken Sie sich diese Leiter – sie ist entscheidend.
Wie entscheidet Rust, welches Trait eine Closure bekommt?
\n\nDer 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
So wirkt sich das auf die Traits aus:
\n\n| Erfassungsart | Implementierte Traits | Beispiel |
|---|---|---|
Nur &T | Fn + FnMut + FnOnce | || println!("{}", x) |
&mut T (aber nicht T) | FnMut + FnOnce | || x += 1 |
T (Besitz) | Nur FnOnce | || drop(x) oder move || x |
Lassen Sie uns jeden Fall mit lebendigem Code durchgehen.
\n\nBeispiel 1: Nur Lesen → Fn
\n\nlet 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\nHier liest die Closure einfach x. Sie muss es weder ändern noch übernehmen. Rust versteht: „Okay, das ist sicher – wir geben Fn“.
Beispiel 2: Veränderung → FnMut
\n\nlet 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\nBeachten Sie: Wir haben mut_closure als let mut deklariert. Warum? Weil der Aufruf von FnMut eine veränderliche