Modèles de conception en Rust : de la théorie à la pratique
Les modèles de conception sont des solutions éprouvées aux problèmes typiques du développement logiciel. En Rust, grâce à son système unique de possession (ownership) et à l'absence d'POO classique, de nombreux modèles classiques prennent une nouvelle dimension. Dans cet article, nous allons analyser quatre modèles clés : Singleton, Factory, Builder et Strategy, adaptés au Rust idiomatique.
1. Singleton en Rust
Rust n'a pas de variables statiques globales au sens habituel. Cependant, Singleton peut être implémenté via lazy_static ou OnceCell. Considérons un exemple avec std::sync::OnceLock (disponible depuis Rust 1.70).
Implémentation avec 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);}Point clé : OnceLock garantit une initialisation unique et la sécurité des threads. C'est une manière idiomatique pour Rust.
2. Factory via les traits
Le modèle Factory en Rust s'implémente parfaitement à l'aide de traits. Nous n'avons pas besoin de classes abstraites — il suffit de définir une interface via un trait.
Exemple : création de véhicules
trait Transport { fn deliver(&self);}
struct Truck;impl Transport for Truck { fn deliver(&self) { println!("Livraison par camion sur terre"); }}
struct Ship;impl Transport for Ship { fn deliver(&self) { println!("Livraison par bateau sur mer"); }}
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!("Type d'itinéraire inconnu"), } }}
fn main() { let transport = LogisticsFactory::create_transport("sea"); transport.deliver();}Avantage : Rust n'exige pas d'héritage — le polymorphisme est atteint via les traits et Box<dyn Trait>.
3. Builder avec chaîne typée
Builder en Rust est utilisé partout, surtout pour les structures avec de nombreux paramètres optionnels. Implémentons-le avec une chaîne typée de méthodes retournant &mut Self.
Exemple : création d'une requête HTTP
#[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 { // Ici on peut ajouter une validation HttpRequest { ..self.request.clone() } }}
fn ma