Zum Inhalt springen

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.

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 = 10
x = 5
▶ Selbst ausprobieren

y 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.

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:

10
▶ Selbst ausprobieren

Umgekehrt 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.

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 = 49
▶ Selbst ausprobieren

Auf zwei Details musst du achten:

  • Die letzte Zeile price + tax + shipping hat kein Semikolon. Setzt du eines, liefert der Block () (nichts) statt einer Zahl zurück, und println! 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.

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:

hallo
▶ Selbst ausprobieren

Genau deshalb braucht Rust keinen Garbage Collector: Es weiß schon beim Kompilieren, wo jeder Scope endet.

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
▶ Selbst ausprobieren

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 = 12
▶ Selbst ausprobieren

area 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.

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 1
Ebene 2
Ebene 3
zurück auf Ebene 2
zurück auf Ebene 1
▶ Selbst ausprobieren

Jedes 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.

  • 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

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