Alle Orte, an denen Muster verwendet werden können

RustRustBeginner
Jetzt üben

This tutorial is from open-source community. Access the source code

💡 Dieser Artikel wurde von AI-Assistenten übersetzt. Um die englische Version anzuzeigen, können Sie hier klicken

Einführung

Willkommen zu All the Places Patterns Can Be Used. Dieser Lab ist ein Teil des Rust Buchs. Du kannst deine Rust-Fähigkeiten in LabEx üben.

In diesem Lab werden wir alle Orte erkunden, an denen Muster in Rust verwendet werden können.

All the Places Patterns Can Be Used

Muster tauchen an mehreren Stellen in Rust auf, und du hast sie schon oft verwendet, ohne es zu bemerken! In diesem Abschnitt werden alle Stellen besprochen, an denen Muster gültig sind.

match-Arme

Wie in Kapitel 6 besprochen, verwenden wir Muster in den Armen von match-Ausdrücken. Formal sind match-Ausdrücke definiert als das Schlüsselwort match, einen Wert, auf den gematcht werden soll, und einen oder mehrere match-Arme, die aus einem Muster und einem Ausdruck bestehen, der ausgeführt werden soll, wenn der Wert dem Muster des Arms entspricht, wie folgt:

match WERT {
    MUSTER => AUSDRUCK,
    MUSTER => AUSDRUCK,
    MUSTER => AUSDRUCK,
}

Beispielsweise ist hier der match-Ausdruck aus Listing 6-5, der auf einen Option<i32>-Wert in der Variable x gematcht wird:

match x {
    None => None,
    Some(i) => Some(i + 1),
}

Die Muster in diesem match-Ausdruck sind die None und Some(i) links von jedem Pfeil.

Eine Anforderung für match-Ausdrücke ist, dass sie ausführlich sein müssen, in dem Sinne, dass alle Möglichkeiten für den Wert im match-Ausdruck berücksichtigt werden müssen. Ein Weg, um sicherzustellen, dass Sie jede Möglichkeit abgedeckt haben, ist, einen Fallback-Muster für den letzten Arm zu haben: Beispielsweise kann ein Variablennamen, der jedem Wert entspricht, niemals fehlschlagen und deckt somit jeden verbleibenden Fall ab.

Das besondere Muster _ wird mit allem übereinstimmen, bindet jedoch niemals an eine Variable, weshalb es oft im letzten match-Arm verwendet wird. Das _-Muster kann nützlich sein, wenn Sie beispielsweise alle Werte ignorieren möchten, die nicht angegeben sind. Wir werden das _-Muster im Abschnitt "Ignorieren von Werten in einem Muster" im Detail behandeln.

Konditionale if let-Ausdrücke

Im Kapitel 6 haben wir diskutiert, wie man if let-Ausdrücke hauptsächlich als eine kürzere Schreibweise für das Äquivalent eines match verwendet, das nur einen Fall abdeckt. Optionalerweise kann if let einen entsprechenden else enthalten, der Code enthält, der ausgeführt wird, wenn das Muster in if let nicht übereinstimmt.

Listing 18-1 zeigt, dass es auch möglich ist, if let, else if und else if let-Ausdrücke zu kombinieren. Dadurch haben wir mehr Flexibilität als bei einem match-Ausdruck, bei dem wir nur einen Wert angeben können, der mit den Mustern verglichen werden soll. Außerdem erfordert Rust nicht, dass die Bedingungen in einer Reihe von if let, else if und else if let-Armen miteinander in Beziehung stehen.

Der Code in Listing 18-1 bestimmt, welche Farbe für den Hintergrund verwendet werden soll, basierend auf einer Reihe von Prüfungen für mehrere Bedingungen. Für dieses Beispiel haben wir Variablen mit hartcodierten Werten erstellt, die ein reales Programm möglicherweise von der Benutzereingabe erhält.

Dateiname: src/main.rs

fn main() {
    let favorite_color: Option<&str> = None;
    let is_tuesday = false;
    let age: Result<u8, _> = "34".parse();

  1 if let Some(color) = favorite_color {
      2 println!(
            "Using your favorite, {color}, as the background"
        );
  3 } else if is_tuesday {
      4 println!("Tuesday is green day!");
  5 } else if let Ok(age) = age {
      6 if age > 30 {
          7 println!("Using purple as the background color");
        } else {
          8 println!("Using orange as the background color");
        }
  9 } else {
     10 println!("Using blue as the background color");
    }
}

Listing 18-1: Kombinieren von if let, else if, else if let und else

Wenn der Benutzer eine Lieblingsfarbe angibt [1], wird diese Farbe als Hintergrund verwendet [2]. Wenn keine Lieblingsfarbe angegeben ist und heute Dienstag ist [3], ist die Hintergrundfarbe grün [4]. Andernfalls, wenn der Benutzer sein Alter als Zeichenfolge angibt und wir es erfolgreich als Zahl analysieren können [5], ist die Farbe entweder lila [7] oder orange [8], je nachdem, was der Wert der Zahl ist [6]. Wenn keine dieser Bedingungen zutrifft [9], ist die Hintergrundfarbe blau [10].

Diese bedingte Struktur ermöglicht es uns, komplexe Anforderungen zu unterstützen. Mit den hier verwendeten hartcodierten Werten wird dieses Beispiel Using purple as the background color ausgeben.

Sie können sehen, dass if let auch in der gleichen Weise wie match-Arme shadowed Variablen einführen kann: Die Zeile if let Ok(age) = age [5] führt eine neue shadowed age-Variable ein, die den Wert innerhalb der Ok-Variante enthält. Dies bedeutet, dass wir die Bedingung if age > 30 [6] innerhalb dieses Blocks platzieren müssen: Wir können diese beiden Bedingungen nicht zu if let Ok(age) = age && age > 30 kombinieren. Die shadowed age, mit der wir mit 30 vergleichen möchten, ist erst gültig, wenn der neue Gültigkeitsbereich mit der geschweiften Klammer beginnt.

Der Nachteil von if let-Ausdrücken ist, dass der Compiler nicht auf Vollständigkeit überprüft, während dies bei match-Ausdrücken der Fall ist. Wenn wir den letzten else-Block [9] weglassen und somit einige Fälle unbehandelt lassen, wird uns der Compiler nicht auf den möglichen Logikfehler hinweisen.

while let bedingte Schleifen

Ähnlich aufgebaut wie if let, erlaubt die while let bedingte Schleife, dass eine while-Schleife solange läuft, wie ein Muster weiterhin übereinstimmt. In Listing 18-2 schreiben wir eine while let-Schleife, die einen Vektor als Stapel verwendet und die Werte im Vektor in umgekehrter Reihenfolge ausgibt, in der sie hinzugefügt wurden.

Dateiname: src/main.rs

let mut stack = Vec::new();

stack.push(1);
stack.push(2);
stack.push(3);

while let Some(top) = stack.pop() {
    println!("{top}");
}

Listing 18-2: Verwenden einer while let-Schleife, um Werte auszugeben, solange stack.pop() Some zurückgibt

Dieses Beispiel gibt 3, 2 und dann 1 aus. Die pop-Methode nimmt das letzte Element aus dem Vektor und gibt Some(value) zurück. Wenn der Vektor leer ist, gibt pop None zurück. Die while-Schleife führt den Code in ihrem Block solange aus, wie pop Some zurückgibt. Wenn pop None zurückgibt, stoppt die Schleife. Wir können while let verwenden, um jedes Element von unserem Stapel zu entfernen.

for-Schleifen

In einer for-Schleife ist der Wert, der direkt nach dem Schlüsselwort for steht, ein Muster. Beispielsweise ist in for x in y das x das Muster. Listing 18-3 zeigt, wie man in einer for-Schleife ein Muster verwendet, um ein Tupel zu zerlegen oder auseinanderzubrechen, als Teil der for-Schleife.

Dateiname: src/main.rs

let v = vec!['a', 'b', 'c'];

for (index, value) in v.iter().enumerate() {
    println!("{value} is at index {index}");
}

Listing 18-3: Verwenden eines Musters in einer for-Schleife, um ein Tupel zu zerlegen

Der Code in Listing 18-3 wird Folgendes ausgeben:

a is at index 0
b is at index 1
c is at index 2

Wir verwenden die enumerate-Methode, um einen Iterator anzupassen, sodass er einen Wert und den Index für diesen Wert produziert, die in ein Tupel eingefügt werden. Der erste produzierte Wert ist das Tupel (0, 'a'). Wenn dieser Wert mit dem Muster (index, value) übereinstimmt, wird index 0 und value 'a' sein, was die erste Zeile der Ausgabe druckt.

let-Anweisungen

Vor diesem Kapitel hatten wir nur explizit über das Verwenden von Mustern mit match und if let diskutiert, aber tatsächlich haben wir Mustern auch an anderen Stellen verwendet, einschließlich in let-Anweisungen. Beispielsweise betrachten Sie diese einfache Variablenzuweisung mit let:

let x = 5;

Jedes Mal, wenn Sie eine let-Anweisung wie diese verwendet haben, haben Sie Mustern verwendet, obwohl Sie das vielleicht nicht bemerkt haben! Formeller sieht eine let-Anweisung so aus:

let MUSTER = AUSDRUCK;

In Anweisungen wie let x = 5; mit einem Variablennamen in der MUSTER-Slot ist der Variablennamen nur eine besonders einfache Form eines Musters. Rust vergleicht den Ausdruck mit dem Muster und weist allen gefundenen Namen zu. Also bedeutet in dem Beispiel let x = 5;, dass x ein Muster ist, das bedeutet: "Binde das, was hier übereinstimmt, an die Variable x." Da der Name x das gesamte Muster ist, bedeutet dieses Muster effektiv: "Binde alles an die Variable x, was auch immer der Wert ist."

Um den musterabgleichenden Aspekt von let deutlicher zu sehen, betrachten Sie Listing 18-4, das ein Muster mit let verwendet, um ein Tupel aufzulösen.

let (x, y, z) = (1, 2, 3);

Listing 18-4: Verwenden eines Musters, um ein Tupel aufzulösen und gleichzeitig drei Variablen zu erstellen

Hier vergleichen wir ein Tupel mit einem Muster. Rust vergleicht den Wert (1, 2, 3) mit dem Muster (x, y, z) und erkennt, dass der Wert mit dem Muster übereinstimmt, in dem er sieht, dass die Anzahl der Elemente in beiden gleich ist. Daher bindet Rust 1 an x, 2 an y und 3 an z. Man kann sich dieses Tupelmuster als das Verschachteln von drei einzelnen Variablenmustern darin vorstellen.

Wenn die Anzahl der Elemente im Muster nicht mit der Anzahl der Elemente im Tupel übereinstimmt, stimmt der gesamte Typ nicht überein und wir erhalten einen Compilerfehler. Beispielsweise zeigt Listing 18-5 einen Versuch, ein Tupel mit drei Elementen in zwei Variablen aufzulösen, was nicht funktioniert.

let (x, y) = (1, 2, 3);

Listing 18-5: Falsches Konstruieren eines Musters, dessen Variablen nicht mit der Anzahl der Elemente im Tupel übereinstimmen

Beim Versuch, diesen Code zu kompilieren, tritt dieser Typfehler auf:

error[E0308]: mismatched types
 --> src/main.rs:2:9
  |
2 |     let (x, y) = (1, 2, 3);
  |         ^^^^^^   --------- this expression has type `({integer}, {integer},
{integer})`
  |         |
  |         expected a tuple with 3 elements, found one with 2 elements
  |
  = note: expected tuple `({integer}, {integer}, {integer})`
             found tuple `(_, _)`

Um den Fehler zu beheben, können wir einen oder mehrere der Werte im Tupel mit _ oder .. ignorieren, wie Sie in "Ignoring Values in a Pattern" sehen werden. Wenn das Problem darin besteht, dass wir zu viele Variablen im Muster haben, ist die Lösung, die Typen so zu passen, dass wir Variablen entfernen, sodass die Anzahl der Variablen der Anzahl der Elemente im Tupel entspricht.

Funktionsparameter

Funktionsparameter können ebenfalls Muster sein. Der Code in Listing 18-6, der eine Funktion namens foo deklariert, die einen Parameter namens x vom Typ i32 annimmt, sollte Ihnen jetzt vertraut vorkommen.

fn foo(x: i32) {
    // code goes here
}

Listing 18-6: Eine Funktionssignatur, die Muster in den Parametern verwendet

Der Teil x ist ein Muster! Wie bei let konnten wir ein Tupel in den Argumenten einer Funktion mit dem Muster abgleichen. Listing 18-7 teilt die Werte in einem Tupel auf, wenn wir es an eine Funktion übergeben.

Dateiname: src/main.rs

fn print_coordinates(&(x, y): &(i32, i32)) {
    println!("Current location: ({x}, {y})");
}

fn main() {
    let point = (3, 5);
    print_coordinates(&point);
}

Listing 18-7: Eine Funktion mit Parametern, die ein Tupel aufspalten

Dieser Code gibt Current location: (3, 5) aus. Die Werte &(3, 5) entsprechen dem Muster &(x, y), sodass x der Wert 3 und y der Wert 5 ist.

Wir können auch Muster in Closure-Parameterlisten auf die gleiche Weise wie in Funktionsparameterlisten verwenden, da Closures ähnlich wie Funktionen sind, wie im Kapitel 13 diskutiert.

An diesem Punkt haben Sie verschiedene Möglichkeiten gesehen, Muster zu verwenden, aber Muster funktionieren nicht auf die gleiche Weise überall, wo wir sie verwenden können. An einigen Stellen müssen die Muster unwiderlegbar sein; in anderen Umständen können sie widerlegbar sein. Wir werden diese beiden Konzepte im nächsten Abschnitt diskutieren.

Zusammenfassung

Herzlichen Glückwunsch! Sie haben das Lab "Alle Orte, an denen Muster verwendet werden können" abgeschlossen. Sie können in LabEx weitere Labs ausprobieren, um Ihre Fähigkeiten zu verbessern.