Das impl Trait-Verhalten in Rust erkunden

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

In diesem Lab werden wir die Verwendung von impl Trait in Rust untersuchen, das als Argumenttyp oder Rückgabetyp verwendet werden kann, was die Funktionsdeklarationen vereinfacht und für flexiblere und präzisere Code ermöglicht.

Hinweis: Wenn das Lab keinen Dateinamen angibt, können Sie einen beliebigen Dateinamen verwenden. Beispielsweise können Sie main.rs verwenden und es mit rustc main.rs &&./main kompilieren und ausführen.

impl Trait

impl Trait kann an zwei Stellen verwendet werden:

  1. als Argumenttyp
  2. als Rückgabetyp

Als Argumenttyp

Wenn Ihre Funktion über einen Trait generisch ist, aber Sie sich nicht um den spezifischen Typ kümmern, können Sie die Funktionsdeklaration mit impl Trait als Typ des Arguments vereinfachen.

Betrachten Sie beispielsweise folgenden Code:

fn parse_csv_document<R: std::io::BufRead>(src: R) -> std::io::Result<Vec<Vec<String>>> {
    src.lines()
     .map(|line| {
            // Für jede Zeile in der Quelle
            line.map(|line| {
                // Wenn die Zeile erfolgreich gelesen wurde, verarbeiten Sie sie, andernfalls geben Sie den Fehler zurück
                line.split(',') // Teilen Sie die Zeile anhand von Kommas auf
                 .map(|entry| String::from(entry.trim())) // Entfernen Sie führende und abschließende Leerzeichen
                 .collect() // Sammeln Sie alle Zeichenketten in einer Zeile in einen Vec<String>
            })
        })
     .collect() // Sammeln Sie alle Zeilen in einen Vec<Vec<String>>
}

parse_csv_document ist generisch und kann daher jeden Typ akzeptieren, der BufRead implementiert, wie z. B. BufReader<File> oder [u8], aber es ist nicht wichtig, welchen Typ R hat, und R wird nur verwendet, um den Typ von src zu deklarieren. Daher kann die Funktion auch wie folgt geschrieben werden:

fn parse_csv_document(src: impl std::io::BufRead) -> std::io::Result<Vec<Vec<String>>> {
    src.lines()
     .map(|line| {
            // Für jede Zeile in der Quelle
            line.map(|line| {
                // Wenn die Zeile erfolgreich gelesen wurde, verarbeiten Sie sie, andernfalls geben Sie den Fehler zurück
                line.split(',') // Teilen Sie die Zeile anhand von Kommas auf
                 .map(|entry| String::from(entry.trim())) // Entfernen Sie führende und abschließende Leerzeichen
                 .collect() // Sammeln Sie alle Zeichenketten in einer Zeile in einen Vec<String>
            })
        })
     .collect() // Sammeln Sie alle Zeilen in einen Vec<Vec<String>>
}

Beachten Sie, dass das Verwenden von impl Trait als Argumenttyp bedeutet, dass Sie nicht explizit angeben können, welche Form der Funktion Sie verwenden. D. h., parse_csv_document::<std::io::Empty>(std::io::empty()) funktioniert nicht mit dem zweiten Beispiel.

Als Rückgabetyp

Wenn Ihre Funktion einen Typ zurückgibt, der MyTrait implementiert, können Sie seinen Rückgabetyp als -> impl MyTrait schreiben. Dies kann die Typensignatur erheblich vereinfachen!

use std::iter;
use std::vec::IntoIter;

// Diese Funktion kombiniert zwei `Vec<i32>` und gibt einen Iterator darüber zurück.
// Schauen Sie sich an, wie kompliziert sein Rückgabetyp ist!
fn combine_vecs_explicit_return_type(
    v: Vec<i32>,
    u: Vec<i32>,
) -> iter::Cycle<iter::Chain<IntoIter<i32>, IntoIter<i32>>> {
    v.into_iter().chain(u.into_iter()).cycle()
}

// Dies ist die genaue gleiche Funktion, aber ihr Rückgabetyp verwendet `impl Trait`.
// Schauen Sie sich an, wie viel einfacher es ist!
fn combine_vecs(
    v: Vec<i32>,
    u: Vec<i32>,
) -> impl Iterator<Item=i32> {
    v.into_iter().chain(u.into_iter()).cycle()
}

fn main() {
    let v1 = vec![1, 2, 3];
    let v2 = vec![4, 5];
    let mut v3 = combine_vecs(v1, v2);
    assert_eq!(Some(1), v3.next());
    assert_eq!(Some(2), v3.next());
    assert_eq!(Some(3), v3.next());
    assert_eq!(Some(4), v3.next());
    assert_eq!(Some(5), v3.next());
    println!("all done");
}

Wichtiger noch, einige Rust-Typen können nicht geschrieben werden. Beispielsweise hat jede Closure ihren eigenen namenlosen konkreten Typ. Bevor die impl Trait-Syntax existierte, mussten Sie auf dem Heap allokieren, um eine Closure zurückzugeben. Aber jetzt können Sie es statisch machen, wie folgt:

// Gibt eine Funktion zurück, die `y` zu ihrem Eingang addiert
fn make_adder_function(y: i32) -> impl Fn(i32) -> i32 {
    let closure = move |x: i32| { x + y };
    closure
}

fn main() {
    let plus_one = make_adder_function(1);
    assert_eq!(plus_one(2), 3);
}

Sie können auch impl Trait verwenden, um einen Iterator zurückzugeben, der map oder filter-Closures verwendet! Dies macht das Verwenden von map und filter einfacher. Da Closure-Typen keine Namen haben, können Sie keinen expliziten Rückgabetyp schreiben, wenn Ihre Funktion Iteratoren mit Closures zurückgibt. Aber mit impl Trait können Sie dies leicht tun:

fn double_positives<'a>(numbers: &'a Vec<i32>) -> impl Iterator<Item = i32> + 'a {
    numbers
     .iter()
     .filter(|x| x > &&0)
     .map(|x| x * 2)
}

fn main() {
    let singles = vec![-3, -2, 2, 3];
    let doubles = double_positives(&singles);
    assert_eq!(doubles.collect::<Vec<i32>>(), vec![4, 6]);
}

Zusammenfassung

Herzlichen Glückwunsch! Sie haben das Impl Trait-Lab abgeschlossen. Sie können in LabEx weitere Labs absolvieren, um Ihre Fähigkeiten zu verbessern.