Patrons de conception en Rust avec exemples : niveau avancé
Rust est un langage qui change l'approche de la conception. Les patrons classiques du GoF nécessitent souvent une adaptation en raison du système strict de possession, de durée de vie et de l'absence d'héritage. Dans cet article, nous analyserons des patrons de conception avancés qui non seulement fonctionnent en Rust, mais utilisent ses points forts : sécurité mémoire, abstractions à coût zéro et système de types puissant.
1. Patron Builder avec vérifications à la compilation
Le Builder classique en Rust est généralement implémenté via une structure avec des champs optionnels. À un niveau avancé, nous pouvons utiliser le système de types pour vérifier les champs obligatoires à la compilation. Cela élimine les erreurs d'exécution "champ obligatoire oublié".
L'implémentation via Type State (état de type) permet de rendre le Builder sûr et auto-documenté.
// États pour le Builderstruct Empty;struct WithName(String);struct WithAge(u8);struct Ready(WithName, WithAge);
struct PersonBuilder<State> { state: State,}
impl PersonBuilder<Empty> { pub fn new() -> Self { PersonBuilder { state: Empty } } pub fn name(self, name: &str) -> PersonBuilder<WithName> { PersonBuilder { state: WithName(name.to_string()), } }}
impl PersonBuilder<WithName> { pub fn age(self, age: u8) -> PersonBuilder<Ready> { PersonBuilder { state: Ready(self.state, WithAge(age)), } }}
impl PersonBuilder<Ready> { pub fn build(self) -> Person { Person { name: self.state.0 .0, age: self.state.1 .0, } }}
struct Person { name: String, age: u8,}
fn main() { // Se compile - tous les champs sont définis let person = PersonBuilder::new() .name("Alice") .age(30) .build(); // Ne se compile pas : pas de name() // let person = PersonBuilder::new().age(30).build(); println!("{} a {} ans", person.name, person.age);}Avantage : les erreurs de configuration sont détectées à la compilation, et non à l'exécution. C'est particulièrement précieux pour les objets complexes avec des dizaines de paramètres.
2. Patron RAII et pointeurs intelligents pour la gestion des ressources
RAII (Resource Acquisition Is Initialization) n'est pas seulement un patron, mais une philosophie de Rust. Chaque ressource (mémoire, fichier, socket, verrou) doit être acquise dans le constructeur et libérée dans le destructeur. Rust implémente cela via le trait Drop et le système de possession.
Exemple avancé — création d'une enveloppe RAII pour un mutex avec libération automatique.
use std::ops::Deref;use std::sync::{Arc, Mutex};
struct Guard<T> { data: T,}
impl<T> Drop for Guard<T> { fn drop(&mut self) { println!("Ressource libérée !"); }}
impl<T> Deref for Guard<T> { type Target = T; fn deref(&self) -> &Self::Target { &self.data }}
struct ResourceManager<T> { resource: Arc<Mutex<T>>,}
impl<T> ResourceManager<T> { fn new(data: T) -> Self { ResourceManager { resource: Arc::new(Mutex::new(data)), } } fn acquire(&self) -> Guard<std::sync::MutexGuard<T>> { let guard = self.resource.lock().unwrap(); Guard { data: guard } }}
fn main() { let manager = ResourceManager::new(42); { let guard = manager.acquire(); println!("Valeur : {}", *guard); // guard sort de la portée -> libère automatiquement le mutex } // Affiche "Ressource libérée !" println!("Le mutex est à nouveau disponible");}Idée clé : le destructeur s