Funktionale Programmierung in Rust: Von Iteratoren bis zu Monaden
Rust ist eine multiparadigmatische Sprache, die imperative, objektorientierte und funktionale Stile nahtlos miteinander verbindet. Obwohl Rust keine rein funktionale Sprache (wie Haskell) ist, ermöglicht sein Typsystem und Ownership-Modell das Schreiben von elegantem, sicherem und ausdrucksstarkem Code im funktionalen Stil. In diesem Artikel werden wir die wichtigsten Konzepte der funktionalen Programmierung (FP) in Rust behandeln: Closures, Iteratoren, Pattern Matching sowie die Arbeit mit Option und Result. Das Material ist sowohl für Anfänger als auch für erfahrene Entwickler nützlich, die die funktionalen Möglichkeiten von Rust tiefer verstehen möchten.
Closures und Lambda-Ausdrücke
Closures in Rust sind anonyme Funktionen, die Variablen aus dem umgebenden Gültigkeitsbereich einfangen können. Sie sind die Grundlage des funktionalen Ansatzes, da sie es ermöglichen, Verhalten als Daten zu übergeben.
Die Syntax von Closures ist prägnant: |Parameter| Körper. Closures können an Funktionen übergeben, in Variablen gespeichert und von Funktionen zurückgegeben werden. Es ist wichtig, sich der drei Traits bewusst zu sein, die das Einfangen steuern: Fn (Ausleihen), FnMut (veränderliches Ausleihen) und FnOnce (Konsumieren).
fn main() { // Einfache Closure let add_one = |x: i32| x + 1; println!("{}", add_one(5)); // 6
// Einfangen einer Variable let factor = 10; let multiply = |x| x * factor; println!("{}", multiply(3)); // 30
// Closure mit dem FnOnce-Trait let name = String::from("Rust"); let greet = move || { println!("Hallo, {}!", name); // name wurde verschoben }; greet(); // println!("{}", name); // Fehler! name ist nicht mehr verfügbar}Das Schlüsselwort move erzwingt das Einfangen by value (Verschiebung), was bei der Arbeit mit Multithreading oder bei der Übergabe einer Closure an einen anderen Kontext oft notwendig ist.
Iteratoren: Faule Verarbeitung und Methodenketten
Eine der größten Stärken des funktionalen Stils in Rust sind die leistungsstarken Iterator-Adapter. Anstelle von expliziten for-Schleifen und Variablen-Mutation verwenden Sie Methodenketten: map, filter, fold, collect und andere. Iteratoren in Rust sind faul – sie führen keine Berechnungen durch, bis eine konsumierende Methode (z. B. collect() oder for_each()) aufgerufen wird.
fn main() { let numbers = vec![1, 2, 3, 4, 5, 6];
// Funktionaler Ansatz: Filtern und Transformieren let result: Vec<i32> = numbers .iter() // Iterator erstellen .filter(|&x| x % 2 == 0) // nur gerade Zahlen behalten .map(|x| x * x) // quadrieren .collect(); // in Vektor sammeln
println!("{:?}", result); // [4, 16, 36]
// Summe mit fold (Reduktion) let sum = numbers.iter().fold(0, |acc, x| acc + x); println!("Summe: {}", sum); // 21
// Bedingungsprüfung mit any und all let has_even = numbers.iter().any(|x| x % 2 == 0); println!("Hat gerade Zahlen: {}", has_even); // true}Die Verwendung von Iteratoren macht den Code deklarativ, lesbar und weniger anfällig für Fehler im Zusammenhang mit Indizierung und Array-Grenzen. Der Rust-Compiler optimiert Iterator-Ketten oft zu effizientem Maschinencode, der mit manuellen Schleifen vergleichbar ist.
Pattern Matching und Destrukturierung
Pattern Matching (match) ist das Herzstück der funktionalen Flusskontrolle in Rust. Es ermöglicht die sichere Verarbeitung aller möglichen Datenvarianten, insbesondere in Kombination mit algebraischen Datentypen (enum). Patterns können verschachtelt sein, Bedingungen (guards) enthalten und Strukturen, Tupel und Aufzählungen destrukturieren.
enum Message { Quit, Move { x: i32, y: i32 }, Write(String), ChangeColor(i32, i32, i32),}
fn handle_