Warum es in Rust keine Standardargumente gibt – und wie man das umgeht
\n\nWenn Sie von Python, JavaScript oder C++ zu Rust kommen, sind Sie wahrscheinlich auf eine Überraschung gestoßen: In Rust gibt es keine eingebaute Unterstützung für Standardargumente. Man kann nicht einfach fn greet(name = "Welt") schreiben – der Compiler sagt Ihnen „nein“.
Aber keine Sorge! Rust bietet andere, flexiblere und sicherere Ansätze. In diesem Tutorial behandeln wir 3 bewährte Methoden, um das Verhalten von Standardargumenten zu implementieren. Sie erfahren, wie Sie Option<T>, Strukturen mit Einstellungen und das Builder-Pattern verwenden – und welche Methode Sie für Ihre Aufgabe wählen sollten.
Der Artikel basiert auf einer echten Frage von Stack Overflow (über 50.000 Aufrufe) – wir haben sie für ein Bildungsformat angepasst. Los geht's!
\n\nMethode 1. Option<T> verwenden – die einfachste Variante
\n\nDer einfachste Weg ist, Argumente als Option<T> zu akzeptieren und innerhalb der Funktion Standardwerte einzusetzen.
fn greet(name: Option<&str>) {\n let name = name.unwrap_or("Welt");\n println!("Hallo, {}!", name);\n}\n\nfn main() {\n greet(Some("Anna")); // Hallo, Anna!\n greet(None); // Hallo, Welt!\n}\n\n\nVorteile:
\n- \n
- Minimaler Code \n
- Auch für Anfänger verständlich \n
- Keine zusätzlichen Strukturen erforderlich \n
Nachteile:
\n- \n
- Jeder Aufruf erfordert das Verpacken in
Some(...)oderNone\n - Bei mehreren Parametern wird der Code umständlich \n
Dieser Ansatz eignet sich hervorragend für Funktionen mit 1-2 optionalen Parametern. Aber wenn es mehr sind, leidet die Lesbarkeit.
\n\nMethode 2. Struktur mit Einstellungen – sauber und skalierbar
\n\nWenn die Parameter auf 3, 5 oder 10 anwachsen, kommt eine Konfigurationsstruktur zur Hilfe. Sie erstellen einen separaten Typ, der alle Einstellungen speichert, und implementieren für ihn das Default-Trait.
struct GreetConfig {\n name: String,\n greeting: String,\n excitement_level: u8,\n}\n\nimpl Default for GreetConfig {\n fn default() -> Self {\n Self {\n name: "Welt".to_string(),\n greeting: "Hallo".to_string(),\n excitement_level: 1,\n }\n }\n}\n\nfn greet(config: GreetConfig) {\n let excitement = "!".repeat(config.excitement_level as usize);\n println!("{}, {}{}", config.greeting, config.name, excitement);\n}\n\nfn main() {\n // Standardwerte verwenden\n greet(GreetConfig::default());\n // Ausgabe: Hallo, Welt!\n\n // Nur das ändern, was nötig ist\n greet(GreetConfig {\n name: "Anna".to_string(),\n ..Default::default()\n });\n // Ausgabe: Hallo, Anna!\n\n // Vollständig benutzerdefinierter Aufruf\n greet(GreetConfig {\n name: "Welt".to_string(),\n greeting: "Guten Tag".to_string(),\n excitement_level: 3,\n });\n // Ausgabe: Guten Tag, Welt!!!\n}\n\n\nVorteile:
\n- \n
- Maximale Flexibilität \n
- Neue Felder können hinzugefügt werden, ohne die Funktionssignatur zu ändern \n
- Die Syntax
..Default::default()ist sehr lesbar \n
Nachteile:
\n- \n
- Erfordert mehr Boilerplate-Code \n
- Das
Default-Trait muss explizit implementiert werden \n
Dies ist die ideale Wahl für reale Projekte: Bibliotheken, Konfigurationsfunktionen, komplexe APIs.
\n\nMethode 3. Builder-Pattern – für diejenigen, die Verkettungen lieben
\n\nDas Builder-Pattern ist ein Klassiker im Rust-Ökosystem. Sie erstellen eine Builder-Struktur, die Parameter sammelt und am Ende die Methode build() aufruft.
struct Greeter {\n name: String,\n greeting: String,\n excitement_level: u8,\n}\n\nstruct GreeterBuilder {\n nam