Entwurfsmuster in Rust: von der Theorie zur Praxis
Entwurfsmuster sind bewährte Lösungen für typische Aufgaben in der Softwareentwicklung. In Rust erhalten viele klassische Muster durch das einzigartige Ownership-System und das Fehlen klassischer OOP eine neue Bedeutung. In diesem Artikel betrachten wir vier Schlüsselmuster: Singleton, Factory, Builder und Strategy, angepasst an idiomatisches Rust.
1. Singleton in Rust
In Rust gibt es keine globalen statischen Variablen im herkömmlichen Sinne. Singleton kann jedoch über lazy_static oder OnceCell implementiert werden. Betrachten wir ein Beispiel mit std::sync::OnceLock (verfügbar seit Rust 1.70).
Implementierung mit OnceLock
use std::sync::OnceLock;
static CONFIG: OnceLock<Config> = OnceLock::new();
#[derive(Debug)]struct Config { db_url: String, port: u16,}
fn get_config() -> &'static Config { CONFIG.get_or_init(|| { Config { db_url: String::from("postgres://localhost"), port: 5432, } })}
fn main() { let cfg = get_config(); println!("{:?}", cfg);}Kernpunkt: OnceLock garantiert einmalige Initialisierung und Threadsicherheit. Dies ist ein idiomatischer Weg für Rust.
2. Factory über Traits
Das Factory-Muster lässt sich in Rust hervorragend mit Traits umsetzen. Wir brauchen keine abstrakten Klassen – es reicht, eine Schnittstelle über ein Trait zu definieren.
Beispiel: Erstellung von Transportmitteln
trait Transport { fn deliver(&self);}
struct Truck;impl Transport for Truck { fn deliver(&self) { println!("Lieferung per LKW auf dem Landweg"); }}
struct Ship;impl Transport for Ship { fn deliver(&self) { println!("Lieferung per Schiff auf dem Seeweg"); }}
struct LogisticsFactory;
impl LogisticsFactory { fn create_transport(route_type: &str) -> Box<dyn Transport> { match route_type { "land" => Box::new(Truck), "sea" => Box::new(Ship), _ => panic!("Unbekannter Routentyp"), } }}
fn main() { let transport = LogisticsFactory::create_transport("sea"); transport.deliver();}Vorteil: Rust benötigt keine Vererbung – Polymorphismus wird über Traits und Box<dyn Trait> erreicht.
3. Builder mit typisierter Methodenverkettung
Builder wird in Rust häufig verwendet, besonders für Strukturen mit vielen optionalen Parametern. Wir implementieren ihn mit einer typisierten Methodenverkettung, die &mut Self zurückgibt.
Beispiel: Erstellung einer HTTP-Anfrage
#[derive(Debug, Default)]struct HttpRequest { url: String, method: String, headers: Vec<(String, String)>, body: Option<String>,}
struct HttpRequestBuilder { request: HttpRequest,}
impl HttpRequestBuilder { fn new(url: &str) -> Self { HttpRequestBuilder { request: HttpRequest { url: url.to_string(), method: "GET".to_string(), ..Default::default() }, } }
fn method(&mut self, method: &str) -> &mut Self { self.request.method = method.to_string(); self }
fn header(&mut self, key: &str, value: &str) -> &mut Self { self.request.headers.push((key.to_string(), value.to_string())); self }
fn body(&mut self, body: &str) -> &mut Self { self.request.body = Some(body.to_string()); self }
fn build(&self) -> HttpRequest { // Hier kann Validierung hinzugefügt werden HttpRequest { ..self.request.clone() } }}
fn ma