Grundlagen des Rust-Control Flows

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 Control Flow. Dieser Lab ist ein Teil des Rust Buchs. Du kannst deine Rust-Fähigkeiten in LabEx üben.

In diesem Lab werden wir uns auf die Kontrollstrukturen in Rust konzentrieren, die das Verwenden von if-Ausdrücken und Schleifen beinhaltet, um Code basierend auf Bedingungen auszuführen und Code zu wiederholen, solange eine Bedingung wahr ist.


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL rust(("Rust")) -.-> rust/DataTypesGroup(["Data Types"]) rust(("Rust")) -.-> rust/ControlStructuresGroup(["Control Structures"]) rust(("Rust")) -.-> rust/FunctionsandClosuresGroup(["Functions and Closures"]) rust(("Rust")) -.-> rust/BasicConceptsGroup(["Basic Concepts"]) rust/BasicConceptsGroup -.-> rust/variable_declarations("Variable Declarations") rust/BasicConceptsGroup -.-> rust/mutable_variables("Mutable Variables") rust/DataTypesGroup -.-> rust/boolean_type("Boolean Type") rust/DataTypesGroup -.-> rust/string_type("String Type") rust/ControlStructuresGroup -.-> rust/for_loop("for Loop") rust/FunctionsandClosuresGroup -.-> rust/function_syntax("Function Syntax") rust/FunctionsandClosuresGroup -.-> rust/expressions_statements("Expressions and Statements") subgraph Lab Skills rust/variable_declarations -.-> lab-100391{{"Grundlagen des Rust-Control Flows"}} rust/mutable_variables -.-> lab-100391{{"Grundlagen des Rust-Control Flows"}} rust/boolean_type -.-> lab-100391{{"Grundlagen des Rust-Control Flows"}} rust/string_type -.-> lab-100391{{"Grundlagen des Rust-Control Flows"}} rust/for_loop -.-> lab-100391{{"Grundlagen des Rust-Control Flows"}} rust/function_syntax -.-> lab-100391{{"Grundlagen des Rust-Control Flows"}} rust/expressions_statements -.-> lab-100391{{"Grundlagen des Rust-Control Flows"}} end

Kontrollstrukturen

Das Vermögen, bestimmten Code abhängig davon auszuführen, ob eine Bedingung true ist, und bestimmten Code wiederholt auszuführen, solange eine Bedingung true ist, sind grundlegende Bausteine in den meisten Programmiersprachen. Die am häufigsten verwendeten Konstrukte, mit denen Sie die Ausführung von Rust-Code steuern können, sind if-Ausdrücke und Schleifen.

if-Ausdrücke

Ein if-Ausdruck ermöglicht es Ihnen, Ihren Code abhängig von Bedingungen zu verzweigen. Sie geben eine Bedingung an und sagen dann: "Wenn diese Bedingung erfüllt ist, führe diesen Codeblock aus. Wenn die Bedingung nicht erfüllt ist, führe diesen Codeblock nicht aus."

Erstellen Sie ein neues Projekt namens branches im Verzeichnis project, um den if-Ausdruck zu erkunden. Öffnen Sie in der Datei src/main.rs den Editor und geben Sie Folgendes ein:

cd ~/project
cargo new branches

Dateiname: src/main.rs

fn main() {
    let number = 3;

    if number < 5 {
        println!("condition was true");
    } else {
        println!("condition was false");
    }
}

Alle if-Ausdrücke beginnen mit dem Schlüsselwort if, gefolgt von einer Bedingung. In diesem Fall prüft die Bedingung, ob die Variable number einen Wert kleiner als 5 hat. Wir platzieren den Codeblock, der ausgeführt werden soll, wenn die Bedingung true ist, direkt nach der Bedingung in geschweiften Klammern. Die Codeblöcke, die mit den Bedingungen in if-Ausdrücken assoziiert sind, werden manchmal als Arme bezeichnet, ähnlich wie die Arme in match-Ausdrücken, über die wir in "Vergleichen der Vermutung mit der Geheimzahl" gesprochen haben.

Optional können wir auch einen else-Ausdruck hinzufügen, wie wir es hier getan haben, um dem Programm einen alternativen Codeblock zur Verfügung zu stellen, der ausgeführt werden soll, wenn die Bedingung false ausgewertet wird. Wenn Sie keinen else-Ausdruck angeben und die Bedingung false ist, springt das Programm einfach den if-Block über und geht zum nächsten Codeabschnitt über.

Versuchen Sie, diesen Code auszuführen; Sie sollten die folgende Ausgabe sehen:

$ cargo run
   Compiling branches v0.1.0 (file:///projects/branches)
    Finished dev [unoptimized + debuginfo] target(s) in 0.31s
     Running `target/debug/branches`
condition was true

Lassen Sie uns versuchen, den Wert von number zu ändern, um eine Bedingung zu erhalten, die false ist, um zu sehen, was passiert:

    let number = 7;

Führen Sie das Programm erneut aus und betrachten Sie die Ausgabe:

$ cargo run
   Compiling branches v0.1.0 (file:///projects/branches)
    Finished dev [unoptimized + debuginfo] target(s) in 0.31s
     Running `target/debug/branches`
condition was false

Es ist auch erwähnenswert, dass die Bedingung in diesem Code muss ein bool sein. Wenn die Bedingung kein bool ist, erhalten wir einen Fehler. Versuchen Sie beispielsweise, den folgenden Code auszuführen:

Dateiname: src/main.rs

fn main() {
    let number = 3;

    if number {
        println!("number was three");
    }
}

Die if-Bedingung wertet sich diesmal zu einem Wert von 3 aus, und Rust wirft einen Fehler:

$ cargo run
   Compiling branches v0.1.0 (file:///projects/branches)
error[E0308]: mismatched types
 --> src/main.rs:4:8
  |
4 |     if number {
  |        ^^^^^^ expected `bool`, found integer

Der Fehler gibt an, dass Rust einen bool erwartet hat, aber eine Ganzzahl erhalten hat. Im Gegensatz zu Sprachen wie Ruby und JavaScript versucht Rust nicht automatisch, nicht-Boolean-Typen in einen Boolean zu konvertieren. Sie müssen explizit sein und if immer mit einem Boolean als Bedingung angeben. Wenn wir möchten, dass der if-Codeblock nur dann ausgeführt wird, wenn eine Zahl nicht gleich 0 ist, können wir den if-Ausdruck wie folgt ändern:

Dateiname: src/main.rs

fn main() {
    let number = 3;

    if number!= 0 {
        println!("number was something other than zero");
    }
}

Beim Ausführen dieses Codes wird number was something other than zero ausgegeben.

Mehrere Bedingungen mit else if behandeln

Sie können mehrere Bedingungen kombinieren, indem Sie if und else in einem else if-Ausdruck verwenden. Beispielsweise:

Dateiname: src/main.rs

fn main() {
    let number = 6;

    if number % 4 == 0 {
        println!("number is divisible by 4");
    } else if number % 3 == 0 {
        println!("number is divisible by 3");
    } else if number % 2 == 0 {
        println!("number is divisible by 2");
    } else {
        println!("number is not divisible by 4, 3, or 2");
    }
}

Dieses Programm hat vier mögliche Pfade, die es einschlagen kann. Nachdem Sie es ausgeführt haben, sollten Sie die folgende Ausgabe sehen:

$ cargo run
   Compiling branches v0.1.0 (file:///projects/branches)
    Finished dev [unoptimized + debuginfo] target(s) in 0.31s
     Running `target/debug/branches`
number is divisible by 3

Wenn dieses Programm ausgeführt wird, prüft es nacheinander jede if-Bedingung und führt den ersten Codeblock aus, für den die Bedingung true ausgewertet wird. Beachten Sie, dass obwohl 6 durch 2 teilbar ist, wir weder die Ausgabe number is divisible by 2 noch den Text number is not divisible by 4, 3, or 2 aus dem else-Block sehen. Das liegt daran, dass Rust nur den Codeblock für die erste true-Bedingung ausführt und sobald er eine findet, er nicht einmal die restlichen prüft.

Das Verwenden zu vieler else if-Ausdrücke kann Ihren Code unübersichtlich machen, daher sollten Sie, wenn Sie mehr als einen haben, Ihren Code möglicherweise umgestalten. Kapitel 6 beschreibt einen leistungsstarken Rust-Zweigungskonstrukt, den match, für diese Fälle.

Verwendung von if in einer let-Anweisung

Da if ein Ausdruck ist, können wir ihn auf der rechten Seite einer let-Anweisung verwenden, um das Ergebnis einer Variable zuzuweisen, wie in Listing 3-2 gezeigt.

Dateiname: src/main.rs

fn main() {
    let condition = true;
    let number = if condition { 5 } else { 6 };

    println!("The value of number is: {number}");
}

Listing 3-2: Zuweisen des Ergebnisses eines if-Ausdrucks an eine Variable

Die Variable number wird anhand des Ergebnisses des if-Ausdrucks gebunden. Führen Sie diesen Code aus, um zu sehen, was passiert:

$ cargo run
   Compiling branches v0.1.0 (file:///projects/branches)
    Finished dev [unoptimized + debuginfo] target(s) in 0.30s
     Running `target/debug/branches`
The value of number is: 5

Denken Sie daran, dass Codeblöcke sich auf den letzten Ausdruck in ihnen auswerten und Zahlen für sich genommen ebenfalls Ausdrücke sind. In diesem Fall hängt der Wert des gesamten if-Ausdrucks davon ab, welcher Codeblock ausgeführt wird. Dies bedeutet, dass die Werte, die als Ergebnisse aus den Armen des if möglich sind, vom gleichen Typ sein müssen; in Listing 3-2 waren die Ergebnisse sowohl des if-Arms als auch des else-Arms i32-Integers. Wenn die Typen nicht übereinstimmen, wie im folgenden Beispiel, erhalten wir einen Fehler:

Dateiname: src/main.rs

fn main() {
    let condition = true;

    let number = if condition { 5 } else { "six" };

    println!("The value of number is: {number}");
}

Wenn wir diesen Code kompilieren möchten, erhalten wir einen Fehler. Die if- und else-Arme haben Werttypen, die nicht kompatibel sind, und Rust zeigt genau an, wo das Problem im Programm zu finden ist:

$ cargo run
   Compiling branches v0.1.0 (file:///projects/branches)
error[E0308]: `if` and `else` have incompatible types
 --> src/main.rs:4:44
  |
4 |     let number = if condition { 5 } else { "six" };
  |                                 -          ^^^^^ expected integer, found
`&str`
  |                                 |
  |                                 expected because of this

Der Ausdruck im if-Block wertet sich zu einem Integer aus, und der Ausdruck im else-Block wertet sich zu einem String aus. Dies funktioniert nicht, da Variablen nur einen einzigen Typ haben dürfen und Rust zur Compile-Zeit wissen muss, welchen Typ die Variable number hat, definitiv. Indem man den Typ von number kennt, kann der Compiler überprüfen, dass der Typ überall dort gültig ist, wo wir number verwenden. Rust wäre nicht in der Lage, das zu tun, wenn der Typ von number nur zur Laufzeit bestimmt würde; der Compiler wäre komplexer und würde über die Codegarantien weniger verfügen, wenn er für jede Variable mehrere hypothetische Typen verfolgen müsste.

Wiederholung mit Schleifen

Es ist oft nützlich, einen Codeblock mehr als einmal auszuführen. Für diese Aufgabe bietet Rust mehrere Schleifen, die den Code innerhalb des Schleifenkörpers bis zum Ende ausführen und dann sofort wieder am Anfang beginnen. Um mit Schleifen zu experimentieren, erstellen wir ein neues Projekt namens loops.

Rust hat drei Arten von Schleifen: loop, while und for. Probieren wir jede aus.

Code mit loop wiederholen

Das Schlüsselwort loop sagt Rust, einen Codeblock ewig wiederholt auszuführen oder bis Sie es explizit zum Stoppen auffordern.

Als Beispiel ändern Sie die Datei src/main.rs im Verzeichnis loops so, dass sie wie folgt aussieht:

Dateiname: src/main.rs

fn main() {
    loop {
        println!("again!");
    }
}

Wenn wir dieses Programm ausführen, werden wir again! kontinuierlich wiederholt ausgegeben, bis wir das Programm manuell beenden. Die meisten Terminals unterstützen die Tastenkombination ctrl-C, um ein in einer Dauerschleife hängen gebliebenes Programm abzubrechen. Probieren Sie es aus:

$ cargo run
   Compiling loops v0.1.0 (file:///projects/loops)
    Finished dev [unoptimized + debuginfo] target(s) in 0.29s
     Running `target/debug/loops`
again!
again!
again!
again!
^Cagain!

Das Symbol ^C repräsentiert den Punkt, an dem Sie ctrl-C gedrückt haben. Je nachdem, wo der Code in der Schleife war, als er das Interrupt-Signal erhielt, können Sie again! nach dem ^C oder nicht sehen.

Glücklicherweise bietet Rust auch eine Möglichkeit, eine Schleife mit Code zu verlassen. Sie können das Schlüsselwort break innerhalb der Schleife platzieren, um dem Programm anzuzeigen, wann es die Schleife beenden soll. Denken Sie daran, dass wir das in das Ratespiel in "Beenden nach einem richtigen Rat" getan haben, um das Programm zu beenden, wenn der Benutzer das richtige Ergebnis erraten hat.

Wir haben auch continue im Ratespiel verwendet, was in einer Schleife dem Programm sagt, den Rest des Codes in dieser Schleifeniteration zu überspringen und zur nächsten Iteration zu gehen.

Rückgabe von Werten aus Schleifen

Eine der Verwendungszwecke eines loop ist es, eine Operation erneut auszuführen, von der Sie wissen, dass sie fehlschlagen kann, wie etwa das Überprüfen, ob ein Thread seine Aufgabe abgeschlossen hat. Möglicherweise müssen Sie auch das Ergebnis dieser Operation aus der Schleife an den Rest Ihres Codes weitergeben. Dazu können Sie den Wert, den Sie zurückgeben möchten, hinter dem break-Ausdruck anfügen, mit dem Sie die Schleife beenden; dieser Wert wird aus der Schleife zurückgegeben, sodass Sie ihn verwenden können, wie hier gezeigt:

fn main() {
    let mut counter = 0;

    let result = loop {
        counter += 1;

        if counter == 10 {
            break counter * 2;
        }
    };

    println!("The result is {result}");
}

Vor der Schleife deklarieren wir eine Variable namens counter und initialisieren sie mit 0. Dann deklarieren wir eine Variable namens result, um den Wert zu speichern, der aus der Schleife zurückgegeben wird. In jeder Iteration der Schleife erhöhen wir die Variable counter um 1 und überprüfen dann, ob counter gleich 10 ist. Wenn dies der Fall ist, verwenden wir das Schlüsselwort break mit dem Wert counter * 2. Nach der Schleife verwenden wir ein Semikolon, um den Ausdruck zu beenden, der den Wert an result zuweist. Schließlich drucken wir den Wert in result, der in diesem Fall 20 ist.

Schleifenbezeichnungen zur Unterscheidung zwischen mehreren Schleifen

Wenn Sie Schleifen innerhalb von Schleifen haben, gelten break und continue für die innerste Schleife an diesem Punkt. Optionaler können Sie einer Schleife eine Schleifenbezeichnung zuweisen, die Sie dann mit break oder continue verwenden können, um anzugeben, dass diese Schlüsselwörter auf die markierte Schleife anstatt auf die innerste Schleife anwenden. Schleifenbezeichnungen müssen mit einem einfachen Anführungszeichen beginnen. Hier ist ein Beispiel mit zwei geschachtelten Schleifen:

fn main() {
    let mut count = 0;
    'counting_up: loop {
        println!("count = {count}");
        let mut remaining = 10;

        loop {
            println!("remaining = {remaining}");
            if remaining == 9 {
                break;
            }
            if count == 2 {
                break 'counting_up;
            }
            remaining -= 1;
        }

        count += 1;
    }
    println!("End count = {count}");
}

Die äußere Schleife hat die Bezeichnung 'counting_up und zählt von 0 bis 2 hoch. Die innere Schleife ohne Bezeichnung zählt von 10 bis 9 runter. Der erste break, der keine Bezeichnung angibt, wird nur die innere Schleife beenden. Die Anweisung break 'counting_up; wird die äußere Schleife beenden. Dieser Code gibt folgendes aus:

   Compiling loops v0.1.0 (file:///projects/loops)
    Finished dev [unoptimized + debuginfo] target(s) in 0.58s
     Running `target/debug/loops`
count = 0
remaining = 10
remaining = 9
count = 1
remaining = 10
remaining = 9
count = 2
remaining = 10
End count = 2

Bedingte Schleifen mit while

Ein Programm muss oft eine Bedingung innerhalb einer Schleife计算评估. Solange die Bedingung true ist, läuft die Schleife. Wenn die Bedingung nicht mehr true ist, ruft das Programm break auf und stoppt die Schleife. Es ist möglich, ein solches Verhalten mit einer Kombination aus loop, if, else und break zu implementieren; Sie können das jetzt in einem Programm versuchen, wenn Sie möchten. Dieses Muster ist jedoch so üblich, dass Rust eine integrierte Sprachkonstruktion dafür hat, die while-Schleife. In Listing 3-3 verwenden wir while, um das Programm drei Mal zu durchlaufen, jedes Mal herunter zu zählen, und dann, nachdem die Schleife beendet ist, eine Nachricht auszugeben und das Programm zu beenden.

Dateiname: src/main.rs

fn main() {
    let mut number = 3;

    while number!= 0 {
        println!("{number}!");

        number -= 1;
    }

    println!("LIFTOFF!!!");
}

Listing 3-3: Verwendung einer while-Schleife, um Code auszuführen, solange eine Bedingung true ist

Dieser Aufbau vermeidet eine Menge Verschachtelungen, die erforderlich wären, wenn Sie loop, if, else und break verwenden, und er ist klarer. Solange eine Bedingung true ist, läuft der Code; andernfalls bricht er die Schleife ab.

Durchlaufen einer Sammlung mit for

Sie können die while-Konstruktion verwenden, um über die Elemente einer Sammlung wie eines Arrays zu iterieren. Beispielsweise druckt die Schleife in Listing 3-4 jedes Element im Array a.

Dateiname: src/main.rs

fn main() {
    let a = [10, 20, 30, 40, 50];
    let mut index = 0;

    while index < 5 {
        println!("the value is: {}", a[index]);

        index += 1;
    }
}

Listing 3-4: Iterieren durch jedes Element einer Sammlung mit einer while-Schleife

Hier zählt der Code durch die Elemente im Array aufwärts. Es startet bei Index 0 und iteriert dann, bis es den letzten Index im Array erreicht hat (d.h., wenn index < 5 nicht mehr true ist). Wenn Sie diesen Code ausführen, werden alle Elemente des Arrays gedruckt:

$ cargo run
   Compiling loops v0.1.0 (file:///projects/loops)
    Finished dev [unoptimized + debuginfo] target(s) in 0.32s
     Running `target/debug/loops`
the value is: 10
the value is: 20
the value is: 30
the value is: 40
the value is: 50

Alle fünf Arraywerte erscheinen im Terminal, wie erwartet. Auch wenn index irgendwann den Wert 5 erreichen wird, stoppt die Schleife vor dem Versuch, einen sechsten Wert aus dem Array abzurufen.

Dieser Ansatz ist jedoch fehleranfällig; wir könnten das Programm zum Absturz bringen, wenn der Indexwert oder die Prüfbedingung falsch ist. Beispielsweise würde der Code abstürzen, wenn Sie die Definition des a-Arrays geändert hätten, um nur vier Elemente zu haben, aber vergessen hätten, die Bedingung auf while index < 4 zu aktualisieren. Es ist auch langsam, weil der Compiler Laufzeitcode hinzufügt, um die bedingte Prüfung durchzuführen, ob der Index innerhalb der Grenzen des Arrays liegt, bei jeder Iteration durch die Schleife.

Als kompaktere Alternative können Sie eine for-Schleife verwenden und für jedes Element in einer Sammlung einige Code ausführen. Eine for-Schleife sieht wie der Code in Listing 3-5 aus.

Dateiname: src/main.rs

fn main() {
    let a = [10, 20, 30, 40, 50];

    for element in a {
        println!("the value is: {element}");
    }
}

Listing 3-5: Iterieren durch jedes Element einer Sammlung mit einer for-Schleife

Wenn wir diesen Code ausführen, werden wir die gleichen Ausgabe wie in Listing 3-4 sehen. Wichtiger ist, dass wir jetzt die Sicherheit des Codes erhöht haben und die Möglichkeit von Fehlern eliminiert haben, die möglicherweise durch das Überschreiten des Arrays oder das Nichtreichen des Endes und das Überspringen einiger Elemente entstehen könnten.

Mit der for-Schleife müssten Sie keine anderen Codeänderungen vornehmen, wenn Sie die Anzahl der Werte im Array ändern, wie Sie es mit der Methode in Listing 3-4 tun müssten.

Die Sicherheit und Kürze von for-Schleifen machen sie zu der am häufigsten verwendeten Schleifenkonstruktion in Rust. Auch in Situationen, in denen Sie einen bestimmten Code eine bestimmte Anzahl von Malen ausführen möchten, wie im Countdown-Beispiel, das in Listing 3-3 eine while-Schleife verwendete, würden die meisten Rust-Entwickler eine for-Schleife verwenden. Die Vorgehensweise dazu wäre, eine Range zu verwenden, die von der Standardbibliothek bereitgestellt wird und alle Zahlen in aufsteigender Reihenfolge von einer Zahl bis zu einer anderen Zahl generiert.

So würde der Countdown aussehen, wenn Sie eine for-Schleife und eine weitere Methode verwenden, die wir noch nicht besprochen haben, rev, um die Reihenfolge umzukehren:

Dateiname: src/main.rs

fn main() {
    for number in (1..4).rev() {
        println!("{number}!");
    }
    println!("LIFTOFF!!!");
}

Dieser Code ist ein bisschen hübscher, oder?

Zusammenfassung

Herzlichen Glückwunsch! Sie haben das Control Flow Lab abgeschlossen. Sie können in LabEx weitere Labs ausprobieren, um Ihre Fähigkeiten zu verbessern.