Einführung in die funktionale Programmierung in Rust
Rust ist eine Multiparadigmensprache, die neben imperativen und objektorientierten Stilen aktiv funktionale Programmierung unterstützt. Integrierte Funktionen wie Closures, Iteratoren, ein leistungsstarkes Typsystem und standardmäßige Unveränderlichkeit ermöglichen es Ihnen, präzisen, sicheren und ausdrucksstarken Code zu schreiben. In diesem Artikel werden wir die wichtigsten Konzepte des funktionalen Ansatzes in Rust erläutern und zeigen, wie man sie in der Praxis anwendet.
Grundlagen des funktionalen Ansatzes: Unveränderlichkeit und reine Funktionen
Funktionale Programmierung basiert auf zwei Säulen: Datenunveränderlichkeit und reinen Funktionen. Rust fördert Unveränderlichkeit — standardmäßig sind alle Variablen unveränderlich. Um einen Wert zu ändern, müssen Sie explizit mut angeben.
fn main() { let x = 5; // unveränderliche Variable // x = 6; // Kompilierungsfehler let mut y = 10; y += 5; // erlaubt, weil y veränderlich ist println!("x = {}, y = {}", x, y);}Eine reine Funktion hat keine Seiteneffekte und gibt für dieselben Argumente immer dasselbe Ergebnis zurück. Rust erlaubt es, solche Funktionen zu schreiben, zwingt Sie aber nicht dazu — Sie können Stile frei mischen.
fn add(a: i32, b: i32) -> i32 { a + b // reine Funktion}Closures — anonyme Funktionen
Closures in Rust sind anonyme Funktionen, die Variablen aus dem umgebenden Gültigkeitsbereich erfassen können. Sie werden mit der Syntax |parameter| rumpf definiert. Closures gibt es in drei Typen, basierend auf der Erfassungsmethode: per Referenz (&T), per veränderlicher Referenz (&mut T) und per Wert (T).
Einfaches Closure-Beispiel
fn main() { let factor = 2; let multiply = |x: i32| x * factor; println!("{}", multiply(10)); // 20}Closure mit veränderlicher Erfassung
fn main() { let mut counter = 0; let mut increment = || { counter += 1; counter }; println!("{}", increment()); // 1 println!("{}", increment()); // 2}Closures werden häufig mit Iteratoren und zur Übergabe von Verhalten an Funktionen höherer Ordnung verwendet.
Iteratoren und funktionale Verkettungen
Iteratoren in Rust sind eine der leistungsstärksten funktionalen Abstraktionen. Anstelle von Schleifen können Sie Ketten von Methoden wie map, filter, fold und anderen erstellen. Iteratoren sind faul — Berechnungen finden nur statt, wenn ein Konsument (z. B. collect oder for_each) aufgerufen wird.
Beispiel: Filtern und Transformation
fn main() { let numbers = vec![1, 2, 3, 4, 5, 6]; let result: Vec<i32> = numbers .iter() .filter(|&x| x % 2 == 0) // gerade Zahlen behalten .map(|x| x * x) // quadrieren .collect(); println!("{:?}", result); // [4, 16, 36]}Fold — Wertakkumulation
fn main() { let numbers = vec![1, 2, 3, 4]; let sum = numbers.iter().fold(0, |acc, x| acc + x); println!("Sum: {}", sum); // 10}Iteratoren machen Code deklarativ: Sie beschreiben, was getan werden muss, nicht wie. Dies verringert die Wahrscheinlichkeit von Fehlern und verbessert die Lesbarkeit.
Pattern Matching
Das match-Konstrukt ist ein zentrales Element des funktionalen Stils in Rust. Es ermöglicht das Destrukturieren komplexer Datenstrukturen, die Behandlung von Option- und Result-Varianten sowie das Schreiben von erschöpfenden Überprüfungen.
Einfaches Beispiel
fn describe_number(n: i32) -> &'static str { match n { 0 => "null", 1 | 2 => "kleine Zahl", 3..=10 => "mittlere Zahl", _ if n < 0 => "negativ",