Ferris Talk #1: Iteratoren in Rust
Seite 3: Zero-Cost Abstractions
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.
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)
- Rust 101 – Einführung in die Programmiersprache Rust, 20. Oktober 2021
- Wasm-Module für den Browser mit Rust, 27. Oktober 2021
- Netzwerk-Applikationen mit dem Tokio Stack, 28. Oktober 2021
Eigene Iteratoren
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));
}