Erweiterte Funktionen und Closures

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 Erweiterte Funktionen und Closures. Dieser Lab ist ein Teil des Rust Buches. Du kannst deine Rust-Fähigkeiten in LabEx üben.

In diesem Lab werden wir erweiterte Funktionsmerkmale und Closures erkunden, einschließlich Funktionszeiger und Rückgabe von Closures.


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL rust(("Rust")) -.-> rust/DataTypesGroup(["Data Types"]) rust(("Rust")) -.-> rust/FunctionsandClosuresGroup(["Functions and Closures"]) rust(("Rust")) -.-> rust/DataStructuresandEnumsGroup(["Data Structures and Enums"]) rust(("Rust")) -.-> rust/AdvancedTopicsGroup(["Advanced Topics"]) rust(("Rust")) -.-> rust/BasicConceptsGroup(["Basic Concepts"]) rust/BasicConceptsGroup -.-> rust/variable_declarations("Variable Declarations") rust/DataTypesGroup -.-> rust/integer_types("Integer Types") rust/FunctionsandClosuresGroup -.-> rust/function_syntax("Function Syntax") rust/FunctionsandClosuresGroup -.-> rust/expressions_statements("Expressions and Statements") rust/DataStructuresandEnumsGroup -.-> rust/method_syntax("Method Syntax") rust/AdvancedTopicsGroup -.-> rust/operator_overloading("Traits for Operator Overloading") subgraph Lab Skills rust/variable_declarations -.-> lab-100450{{"Erweiterte Funktionen und Closures"}} rust/integer_types -.-> lab-100450{{"Erweiterte Funktionen und Closures"}} rust/function_syntax -.-> lab-100450{{"Erweiterte Funktionen und Closures"}} rust/expressions_statements -.-> lab-100450{{"Erweiterte Funktionen und Closures"}} rust/method_syntax -.-> lab-100450{{"Erweiterte Funktionen und Closures"}} rust/operator_overloading -.-> lab-100450{{"Erweiterte Funktionen und Closures"}} end

Erweiterte Funktionen und Closures

In diesem Abschnitt werden einige erweiterte Funktionsmerkmale untersucht, die mit Funktionen und Closures zusammenhängen, einschließlich Funktionszeiger und Rückgabe von Closures.

Funktionszeiger

Wir haben bereits darüber gesprochen, wie man Closures an Funktionen übergibt; man kann auch reguläre Funktionen an Funktionen übergeben! Diese Technik ist nützlich, wenn man eine bereits definierte Funktion übergeben möchte, anstatt einen neuen Closure zu definieren. Funktionen werden in den Typ fn (mit einem kleinen f) umgewandelt, nicht zu verwechseln mit dem Closure-Trait Fn. Der Typ fn wird als Funktionszeiger bezeichnet. Das Übergeben von Funktionen mit Funktionszeigern ermöglicht es Ihnen, Funktionen als Argumente für andere Funktionen zu verwenden.

Die Syntax zum Angeben, dass ein Parameter ein Funktionszeiger ist, ähnelt der von Closures, wie in Listing 19-27 gezeigt, wo wir eine Funktion add_one definiert haben, die 1 zu ihrem Parameter addiert. Die Funktion do_twice nimmt zwei Parameter: einen Funktionszeiger auf jede Funktion, die einen i32-Parameter annimmt und einen i32 zurückgibt, und einen i32-Wert. Die Funktion do_twice ruft die Funktion f zweimal auf, übergibt ihr den arg-Wert und addiert dann die beiden Funktionsaufruf-Ergebnisse zusammen. Die main-Funktion ruft do_twice mit den Argumenten add_one und 5 auf.

Dateiname: src/main.rs

fn add_one(x: i32) -> i32 {
    x + 1
}

fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 {
    f(arg) + f(arg)
}

fn main() {
    let answer = do_twice(add_one, 5);

    println!("The answer is: {answer}");
}

Listing 19-27: Verwendung des Typs fn, um einen Funktionszeiger als Argument zu akzeptieren

Dieser Code gibt The answer is: 12 aus. Wir geben an, dass der Parameter f in do_twice ein fn ist, das einen Parameter vom Typ i32 annimmt und einen i32 zurückgibt. Wir können dann f im Körper von do_twice aufrufen. In main können wir den Funktionsnamen add_one als erstes Argument an do_twice übergeben.

Im Gegensatz zu Closures ist fn ein Typ und nicht ein Trait, daher geben wir fn direkt als Parametertyp an, anstatt einen generischen Typparameter mit einem der Fn-Traits als Trait-Bound zu deklarieren.

Funktionszeiger implementieren alle drei Closure-Traits (Fn, FnMut und FnOnce), was bedeutet, dass Sie immer einen Funktionszeiger als Argument für eine Funktion übergeben können, die einen Closure erwartet. Es ist am besten, Funktionen mit einem generischen Typ und einem der Closure-Traits zu schreiben, damit Ihre Funktionen sowohl Funktionen als auch Closures akzeptieren können.

Allerdings ist ein Beispiel dafür, wo man nur fn und keine Closures akzeptieren möchte, wenn man mit externem Code interagiert, der keine Closures hat: C-Funktionen können Funktionen als Argumente akzeptieren, aber C hat keine Closures.

Als Beispiel dafür, wo man entweder einen inline definierten Closure oder eine benannte Funktion verwenden könnte, betrachten wir die Verwendung der map-Methode, die vom Iterator-Trait in der Standardbibliothek bereitgestellt wird. Um die map-Funktion zu verwenden, um einen Vektor von Zahlen in einen Vektor von Strings umzuwandeln, könnten wir einen Closure verwenden, wie folgt:

let list_of_numbers = vec![1, 2, 3];
let list_of_strings: Vec<String> = list_of_numbers
 .iter()
 .map(|i| i.to_string())
 .collect();

Oder wir könnten eine benannte Funktion als Argument für map angeben, statt des Closures, wie folgt:

let list_of_numbers = vec![1, 2, 3];
let list_of_strings: Vec<String> = list_of_numbers
 .iter()
 .map(ToString::to_string)
 .collect();

Beachten Sie, dass wir die vollqualifizierte Syntax verwenden müssen, über die wir in "Erweiterte Traits" gesprochen haben, da es mehrere Funktionen mit dem Namen to_string gibt.

Hier verwenden wir die to_string-Funktion, die im ToString-Trait definiert ist, die die Standardbibliothek für jeden Typ implementiert hat, der Display implementiert.

Denken Sie sich aus "Enum-Werte" zurück, dass der Name jeder Enum-Variante, die wir definieren, auch eine Initialisierungsfunktion wird. Wir können diese Initialisierungsfunktionen als Funktionszeiger verwenden, die die Closure-Traits implementieren, was bedeutet, dass wir die Initialisierungsfunktionen als Argumente für Methoden angeben können, die Closures erwarten, wie folgt:

enum Status {
    Value(u32),
    Stop,
}

let list_of_statuses: Vec<Status> = (0u32..20)
 .map(Status::Value)
 .collect();

Hier erstellen wir Status::Value-Instanzen, indem wir jede u32-Wert im Bereich verwenden, auf dem map aufgerufen wird, indem wir die Initialisierungsfunktion von Status::Value verwenden. Einige Leute bevorzugen diesen Stil und einige Leute bevorzugen es, Closures zu verwenden. Sie kompilieren zu demselben Code, daher verwenden Sie den Stil, der Ihnen am klarsten ist.

Rückgabe von Closures

Closures werden durch Traits repräsentiert, was bedeutet, dass Sie Closures nicht direkt zurückgeben können. In den meisten Fällen, in denen Sie einen Trait zurückgeben möchten, können Sie stattdessen den konkreten Typ verwenden, der den Trait implementiert, als Rückgabewert der Funktion. Mit Closures können Sie das jedoch nicht tun, da sie keinen konkreten Typ haben, der zurückgegeben werden kann; Sie dürfen beispielsweise nicht den Funktionszeiger fn als Rückgabetyp verwenden.

Der folgende Code versucht, einen Closure direkt zurückzugeben, aber er wird nicht kompilieren:

fn returns_closure() -> dyn Fn(i32) -> i32 {
    |x| x + 1
}

Der Compilerfehler lautet wie folgt:

error[E0746]: return type cannot have an unboxed trait object
 --> src/lib.rs:1:25
  |
1 | fn returns_closure() -> dyn Fn(i32) -> i32 {
  |                         ^^^^^^^^^^^^^^^^^^ doesn't have a size known at
compile-time
  |
  = note: for information on `impl Trait`, see
<https://doc.rust-lang.org/book/ch10-02-traits.html#returning-types-that-
implement-traits>
help: use `impl Fn(i32) -> i32` as the return type, as all return paths are of
type `[closure@src/lib.rs:2:5: 2:14]`, which implements `Fn(i32) -> i32`
  |
1 | fn returns_closure() -> impl Fn(i32) -> i32 {
  |                         ~~~~~~~~~~~~~~~~~~~

Der Fehler verweist erneut auf den Sized-Trait! Rust weiß nicht, wie viel Speicher es zum Speichern des Closures benötigen wird. Wir haben eine Lösung für dieses Problem bereits gesehen. Wir können ein Traitobjekt verwenden:

fn returns_closure() -> Box<dyn Fn(i32) -> i32> {
    Box::new(|x| x + 1)
}

Dieser Code wird problemlos kompilieren. Weitere Informationen zu Traitobjekten finden Sie in "Using Trait Objects That Allow for Values of Different Types".

Als nächstes schauen wir uns Makros an!

Zusammenfassung

Herzlichen Glückwunsch! Sie haben das Lab zu erweiterten Funktionen und Closures abgeschlossen. Sie können in LabEx weitere Labs absolvieren, um Ihre Fähigkeiten zu verbessern.