Ferris Talk #1: Iteratoren in Rust

Seite 3: Zero-Cost Abstractions

Inhaltsverzeichnis

Die strikten Regeln für Speicherverwaltung in Rust, die bereits der Compiler erzwingt, sorgen dafür, dass der erzeugte Code sehr effizient ist. Dass Rust ohne Garbage Collector auskommt, wirkt sich positiv auf die Performance des Maschinencodes aus, den der Rust-Compiler erstellt. Man "bezahlt" Abstraktionen wie Iteratoren also nicht durch Geschwindigkeitsverlust (Zero-Cost Abstractions). Ich möchte das an einem einfachen Beispiel erklären:

fn main() {
    let mut sum = 0;
    for i in 0..10 {
        sum += i;
    }
    
    println!("{}", sum);
}

Das Beispiel verwendet einen Iterator über die Zahlen Null bis Neun in einer Schleife. Interessant ist ein Blick auf den erzeugten Assemblercode. Denkbar wäre, dass im Hintergrund into_iter aufgerufen wird und die Schleife das Ergebnis Schritt für Schritt durch den Aufruf von next erzeugt. Tatsächlich ist das der Fall, wenn man den Code mit der Debug-Option kompiliert. Beim Umschalten auf den Relase-Modus hingegen bleibt vom Iterator nichts übrig. Tatsächlich erkennt der Compiler, dass das Ergebnis der Schleife schon zur Übersetzungszeit ermittelbar ist. Daher kann er die Schleife inklusive Iterator entfernen und das Ergebnis als Konstante in den Code einbauen.

Auch wenn das Beispiel extrem ist, verdeutlicht es, dass man beim Kompilieren der Release-Version nicht annehmen darf, dass die Iteratoren im Assembler-Code analog zum Rust-Code wiederzufinden sind.

betterCode() Rust – Dein Einstieg und Deep Dive in Rust
Heise-Konferenz zu Rust, Einstieg und Deep Dive, 13. Oktober 2021 online, mit Rainer Stropek und Stefan Baumgartner

Heise richtet am 13. Oktober 2021 eine Online-Konferenz zu Rust für Einsteiger und Experten aus. Die beiden Ferris-Talk-Kolumnisten sind dort mit Vorträgen und Workshops präsent:

Rainer Stropek erklärt das Speichermanagement in Rust und erläutert die Rust-Konzepte Ownership, References, Borrowing und Lifetimes. Er zeigt die Konzepte anhand praktischer Code-Beispiele. Wer teilnimmt, erlangt ein Problembewusstsein der Sicherheitsrelevanz von Speicherverwaltung, baut Verständnis auf für die Rust-Kernkonzepte und erkennt Unterschiede zwischen Rust und anderen gängigen Programmiersprachen.

Stefan Baumgartner widmet sich Serverless Rust und führt vor, wie man Serverless-Workloads mit Rust ausführt. Wer teilnimmt, gewinnt ein tieferes Verständnis von AWS Lambda und Azure Functions, lernt die passenden Rust-Crates für Serverless-Workloads und begreift den unmittelbaren Nutzen von Rust dafür.

Flankierende Workshops von Stropek & Baumgartner (9-17 Uhr)

Um ein tieferes Verständnis für Iteratoren in Rust zu erhalten, entwickeln wir einen eigenen Iterator. Genau genommen handelt es sich um eine spezielle Form eines Iterators: einen Generator. Generatoren haben keine zugrundeliegende Datenstruktur, aus der sie Elemente bei der Iteration zurückgeben. Sie generieren den nächsten Wert erst, wenn sie mit next danach gefragt werden.

Das Beispiel implementiert einen einfachen Passwortgenerator. Es ist nicht das Ziel, kryptografisch starke Passwörter zu generieren, sondern das Prinzip der Rust-Iteratoren zu verdeutlichen. Das Augenmerk liegt im folgenden Codebeispiel auf der Implementierung von IntoIterator für die Struktur Passwords sowie auf der Implementierung von Iterator für PasswordsIterator.

use rand::Rng;

struct Passwords {
    length: usize,
}

impl Passwords {
    fn new() -> Self {
        Self::with_length(10)
    }

    fn with_length(length: usize) -> Self {
        Passwords { length: length }
    }
}

impl IntoIterator for Passwords {
    type Item = String;
    type IntoIter = PasswordsIterator;
    fn into_iter(self) -> Self::IntoIter {
        PasswordsIterator {
            length: self.length,
        }
    }
}

struct PasswordsIterator {
    length: usize,
}

impl Iterator for PasswordsIterator {
    type Item = String;
    fn next(&mut self) -> Option<Self::Item> {
        let mut result = String::with_capacity(self.length);
        for _ in 0..self.length {
            // For demo purposes, we generate a password
            // consisting only of letters a..z
            result.push((b'a' + rand::thread_rng().gen_range(0..=(b'z' - b'a'))) as char);
        }

        Some(result)
    }
}

fn main() {
    // Generate three passwords and print them in a for loop
    for p in Passwords::new().into_iter().take(3) {
        println!("The next password is {}", p);
    }

    // Generate three passwords and print them with for_each
    Passwords::with_length(5)
        .into_iter()
        .take(3)
        .for_each(|p| println!("The next password is {}", p));
}