Gültigkeitsbereich (Scope) in Rust
Der Gültigkeitsbereich (Scope) ist der Teil des Codes, in dem eine Variable gültig ist. Die Idee ist einfach. Wenn du sie verstanden hast, ergeben aber viele andere Regeln von Rust plötzlich Sinn – vor allem das Ownership.
Geschweifte Klammern legen den Scope fest
Abschnitt betitelt „Geschweifte Klammern legen den Scope fest“Eine Variable existiert ab der Stelle, an der sie deklariert wird, bis zur schließenden } des Blocks, in dem sie steht:
fn main() { let x = 5; // ab hier ist x gültig
{ let y = 10; // ab hier ist y gültig println!("x = {x}, y = {y}"); // beide sind gültig } // hier endet der Scope von y
println!("x = {x}"); // println!("{y}"); // ❌ Fehler: y ist nicht mehr gültig}Ausgabe:
x = 5, y = 10x = 5y existiert nur innerhalb des inneren { }-Blocks. Sobald dieser Block endet, ist y weg. Versuchst du danach, y zu benutzen, bekommst du einen Compilerfehler – keine böse Überraschung erst zur Laufzeit.
Der Scope wirkt nur in eine Richtung: Ein innerer Block sieht die Variablen der Blöcke, die ihn umgeben (deshalb kann der innere Block x ausgeben). Ein äußerer Block kann aber nicht in einen inneren hineinschauen.
Auch Funktionsrümpfe sind ein Scope
Abschnitt betitelt „Auch Funktionsrümpfe sind ein Scope“Die Parameter und alle Variablen, die du in einer Funktion anlegst, existieren nur, solange dieser Funktionsaufruf läuft:
fn calculate() -> i32 { let result = 5 * 2; // result existiert nur innerhalb von calculate result}
fn main() { let value = calculate(); println!("{value}"); // println!("{result}"); // ❌ Fehler: result gibt es hier draußen nicht}Ausgabe:
10Umgekehrt gilt dasselbe: Eine Funktion sieht die lokalen Variablen von main nicht. Braucht sie einen Wert, übergibst du ihn als Parameter – so wie du es auf der Seite Funktionen gesehen hast.
Auch Blöcke sind Ausdrücke
Abschnitt betitelt „Auch Blöcke sind Ausdrücke“Ein { }-Block legt nicht nur fest, wo Variablen leben. Er kann auch einen Wert liefern: den letzten Ausdruck darin, geschrieben ohne Semikolon. Das ist dieselbe Regel, nach der eine Funktion ihre letzte Zeile zurückgibt.
Das ist praktisch, wenn du ein paar Hilfsvariablen brauchst, um etwas auszurechnen, sie danach aber nicht herumliegen sollen:
fn main() { let price = 40;
let total = { let tax = price / 10; // tax lebt nur in diesem Block let shipping = 5; price + tax + shipping // kein Semikolon: das ist der Wert des Blocks };
println!("Gesamt = {total}"); // println!("{tax}"); // ❌ Fehler: tax ist außerhalb seines Scopes}Ausgabe:
Gesamt = 49Auf zwei Details musst du achten:
- Die letzte Zeile
price + tax + shippinghat kein Semikolon. Setzt du eines, liefert der Block()(nichts) statt einer Zahl zurück, undprintln!kann das nicht ausgeben. - Das Ganze ist eine
let-Anweisung. Deshalb endet sie nach der schließenden Klammer mit};.
Sobald der Block endet, sind tax und shipping weg. Übrig bleibt nur das Ergebnis, total. Wenn du Hilfsvariablen so in einen kleinen Block packst, ist klar, dass sie nur für diese eine Rechnung gebraucht werden.
Warum der Scope für Ownership wichtig ist
Abschnitt betitelt „Warum der Scope für Ownership wichtig ist“Das hängt direkt mit Ownership zusammen, das dir etwas später begegnet: Ein Wert wird automatisch aufgeräumt, sobald der Scope seines Owners (Eigentümers) endet.
fn main() { { let text = String::from("hallo"); println!("{text}"); } // hier endet der Scope von text – Rust räumt den Wert automatisch auf (drop)}Ausgabe:
halloGenau deshalb braucht Rust keinen Garbage Collector: Es weiß schon beim Kompilieren, wo jeder Scope endet.
Shadowing und Scope
Abschnitt betitelt „Shadowing und Scope“Erinnerst du dich an Shadowing (Überschatten) von der Seite Variablen? Jedes let mit demselben Namen innerhalb eines Scopes erzeugt eine eigene, neue Variable. Die äußere wird dabei nicht gelöscht. Die neue übernimmt nur für den Rest dieses Scopes:
fn main() { let x = 5; { let x = x * 2; // ein neues x, nur in diesem Block println!("inneres x = {x}"); // 10 } println!("äußeres x = {x}"); // 5 – unverändert}Ausgabe:
inneres x = 10äußeres x = 5Übungen
Abschnitt betitelt „Übungen“1. Den Scope-Fehler beheben
Abschnitt betitelt „1. Den Scope-Fehler beheben“Dieses Programm kompiliert nicht, weil area benutzt wird, nachdem sein Block schon zu Ende ist:
fn main() { let width = 3; { let height = 4; let area = width * height; } println!("Fläche = {area}");}Ändere es so, dass es Fläche = 12 ausgibt, während height weiterhin nur innerhalb des Blocks lebt. Tipp: Lass den Block die Fläche liefern und speichere das Ergebnis in einer Variablen, die außerhalb deklariert ist.
Lösung anzeigen
fn main() { let width = 3; let area = { let height = 4; width * height }; println!("Fläche = {area}");}Ausgabe:
Fläche = 12area lebt jetzt im Scope von main, also kann println! darauf zugreifen. height wird weiterhin nur im Block gebraucht und aufgeräumt, wenn der Block endet.
2. Verschachtelungsebenen
Abschnitt betitelt „2. Verschachtelungsebenen“Leg eine Variable level mit dem Wert 1 an. Öffne einen Block, der sie mit 2 überschattet, und darin einen weiteren Block, der sie mit 3 überschattet. Gib level in jedem Block aus und noch einmal direkt nachdem sich jeder Block schließt. Tipp: Die Zahlen sollten 1, 2, 3 lauten und dann wieder auf 2 und 1 zurückgehen.
Lösung anzeigen
fn main() { let level = 1; println!("Ebene {level}"); { let level = 2; println!("Ebene {level}"); { let level = 3; println!("Ebene {level}"); } println!("zurück auf Ebene {level}"); } println!("zurück auf Ebene {level}");}Ausgabe:
Ebene 1Ebene 2Ebene 3zurück auf Ebene 2zurück auf Ebene 1Jedes innere level verdeckt das äußere nur, bis sein Block endet. Die äußeren Variablen wurden nie verändert. Sie sind also noch da und warten.
Zusammenfassung
Abschnitt betitelt „Zusammenfassung“- Der Scope ist der Bereich (markiert durch
{ }), in dem eine Variable gültig ist. - Eine Variable hört in dem Moment auf zu existieren, in dem die schließende
}ihres Scopes erreicht ist. - Auch Funktionsrümpfe sind Scopes – lokale Variablen einer Funktion verschwinden, wenn sie zurückkehrt.
- Ein Block ist ein Ausdruck: Seine letzte Zeile (ohne Semikolon) ist sein Wert, und seine Hilfsvariablen verschwinden, wenn er endet.
- Über den Scope weiß Rust genau, wann ein Wert aufgeräumt werden muss. Deshalb braucht es keinen Garbage Collector.
Kurzer Check
Abschnitt betitelt „Kurzer Check“Kurzer Check
1. Warum braucht Rust laut dieser Seite keinen Garbage Collector?
2. Was passiert, wenn du in einem inneren Block eine Variable mit let x = x * 2; überschattest?
3. Welchen Wert hat total nach let total = { let a = 2; a * 10 };?
Punkte: 0 / 3
Weiterlesen
Abschnitt betitelt „Weiterlesen“- Gültigkeitsbereich (scope) einer Variable – Das Rust-Buch: wie der Scope funktioniert, erklärt als erster Schritt hin zu Ownership.
- Scope and Shadowing – Rust by Example (auf Englisch): ein kurzes, ausführbares Beispiel zu Blöcken und Shadowing.
- RAII – Rust by Example (auf Englisch): was mit Werten passiert, wenn ihr Scope endet – für den nächsten Schritt, wenn du so weit bist.
- Block expressions – The Rust Reference (auf Englisch): die genauen Regeln für Blöcke und die Werte, die sie liefern.