Warum Architektur in Rust kein Luxus, sondern eine Notwendigkeit ist
Wenn Sie in Rust schreiben, bieten Ihnen das Typsystem und der Borrow-Checker bereits 80% Schutz vor Fehlern. Aber Speichersicherheit ist nur das Fundament. Wenn Sie darauf eine chaotische Architektur aufbauen, wird das Projekt früher oder später zu einem Labyrinth aus gegenseitigen Abhängigkeiten, bei dem die Änderung eines Moduls zehn andere zerstört.
Architekturmuster in Rust sind weder eine Modeerscheinung noch ein Relikt der OOP-Welt. Es sind bewährte Lösungen für typische Aufgaben: Wie erstelle ich ein Objekt, das keine Invarianten verletzt? Wie verbinde ich Komponenten, damit sie einzeln testbar sind? Wie organisiere ich Logik, damit sie erweiterbar ist, ohne vorhandenen Code zu ändern?
In diesem Artikel werden wir drei Gruppen von Mustern (Erzeugungs-, Struktur- und Verhaltensmuster) mit realen Beispielen in Rust durchgehen. Keine abstrakte Theorie – nur Code, den Sie sofort anwenden können.
Erzeugungsmuster: Objekte richtig erstellen
Builder – der Retter vor Konstruktoren mit 10 Parametern
In Rust gibt es keine Konstruktorüberladung. Wenn eine Struktur mehr als 3–4 Felder hat, bedeutet das Schreiben eines einzigen Konstruktors für alle Fälle, den Benutzer zum Lesen einer langen Signatur mit Option und Default zu verdammen. Der Builder löst dieses Problem elegant.
struct ServerConfig { host: String, port: u16, workers: u8, enable_tls: bool, max_connections: u32,}
struct ServerConfigBuilder { host: Option<String>, port: Option<u16>, workers: Option<u8>, enable_tls: Option<bool>, max_connections: Option<u32>,}
impl ServerConfigBuilder { fn new() -> Self { Self { host: None, port: None, workers: None, enable_tls: None, max_connections: None, } }
fn host(mut self, host: &str) -> Self { self.host = Some(host.to_string()); self }
fn port(mut self, port: u16) -> Self { self.port = Some(port); self }
fn workers(mut self, workers: u8) -> Self { self.workers = Some(workers); self }
fn enable_tls(mut self, enable: bool) -> Self { self.enable_tls = Some(enable); self }
fn max_connections(mut self, max: u32) -> Self { self.max_connections = Some(max); self }
fn build(self) -> Result<ServerConfig, String> { Ok(ServerConfig { host: self.host.unwrap_or_else(|| "127.0.0.1".to_string()), port: self.port.unwrap_or(8080), workers: self.workers.unwrap_or(4), enable_tls: self.enable_tls.unwrap_or(false), max_connections: self.max_connections.unwrap_or(100), }) }}
// Verwendunglet config = ServerConfigBuilder::new() .host("0.0.0.0") .port(443) .enable_tls(true) .build() .expect("Ungültige Konfiguration");Warum funktioniert das in Rust? Das Builder-Muster harmoniert perfekt mit dem Ownership-System: Die Methodenkette konsumiert und gibt self zurück, und der finale build gibt entweder das fertige Objekt oder einen Validierungsfehler zurück. Keine veränderlichen Referenzen und keine Datenrennen.
Factory – wenn der Rückgabetyp vom Parameter abhängt
Stellen Sie sich vor, Sie müssen verschiedene Formate parsen: JSON, YAML, TOML. Anstatt überall match zu verwenden, nutzen wir eine Factory-Methode.
trait Parser { fn parse(&self, input: &str) -> Result<serde_json::Value, String>;}
struct JsonParser;impl Parser for JsonParser { fn parse(&self, input: &str) -> Result<serde_json::Value, String> { serde_json::from_str(input).map_err(|e| e.to_string()) }}
struct YamlParser;impl Parser for YamlParser { fn parse(&self, input: &str) -> Result<serde_json::