Injection de dépendances (Dependency Injection) en Rust pour débutants

Formateur Python en ligne pour débutants

Apprenez Python facilement sans surcharge théorique. Résolvez des tâches pratiques avec vérification automatique et écrivez du code directement dans le navigateur.

Start Course

Introduction : Pourquoi l'injection de dépendances est-elle importante en Rust ?

\n\n

Lors de l'écriture de programmes en Rust, vous rencontrerez tôt ou tard une situation où un module (par exemple, un service ou un contrôleur) doit utiliser les fonctionnalités d'un autre module (base de données, API, journaliseur). La méthode la plus simple consiste à créer l'objet nécessaire directement à l'intérieur du code qui l'utilise :

\n\n
struct UserService;\n\nimpl UserService {\n    fn get_user(&self, id: u32) -> String {\n        let db = Database::new(); // ❌ Couplage fort\n        db.query_user(id)\n    }\n}\n
\n\n

Cette approche crée un couplage fort (tight coupling). Le code devient difficile à tester, à réutiliser et à modifier. L'injection de dépendances (DI) est un modèle dans lequel les dépendances sont transmises à l'objet de l'extérieur, plutôt que créées à l'intérieur de celui-ci. Rust, étant un langage doté d'un système de possession et de traits, offre des moyens élégants d'implémenter la DI sans frameworks.

\n\n

Dans cet article, nous allons voir comment injecter des dépendances en Rust avec des exemples pratiques : de la simple transmission de structures à l'utilisation de traits et de pointeurs intelligents.

\n\n

1. Approche de base : Transmission des dépendances via le constructeur

\n\n

La façon la plus simple d'injecter une dépendance en Rust est de la passer en argument au constructeur ou à la fonction. Prenons l'exemple d'un journaliseur.

\n\n

Mauvais exemple (sans DI)

\n\n
struct ConsoleLogger;\n\nimpl ConsoleLogger {\n    fn log(&self, msg: &str) {\n        println!("[LOG]: {}", msg);\n    }\n}\n\nstruct OrderService {\n    logger: ConsoleLogger, // ❌ Liaison rigide à un type spécifique\n}\n\nimpl OrderService {\n    fn new() -> Self {\n        Self {\n            logger: ConsoleLogger, // Créé à l'intérieur\n        }\n    }\n\n    fn place_order(&self, item: &str) {\n        self.logger.log(&format!("Commande : {}", item));\n    }\n}\n
\n\n

Bon exemple (avec DI)

\n\n
struct ConsoleLogger;\n\nimpl ConsoleLogger {\n    fn log(&self, msg: &str) {\n        println!("[LOG]: {}", msg);\n    }\n}\n\nstruct OrderService {\n    logger: ConsoleLogger, // La dépendance est transmise de l'extérieur\n}\n\nimpl OrderService {\n    // Le constructeur accepte la dépendance\n    fn new(logger: ConsoleLogger) -> Self {\n        Self { logger }\n    }\n\n    fn place_order(&self, item: &str) {\n        self.logger.log(&format!("Commande : {}", item));\n    }\n}\n\nfn main() {\n    let logger = ConsoleLogger;\n    let service = OrderService::new(logger);\n    service.place_order("Livre sur Rust");\n}\n
\n\n

Qu'est-ce qui a changé ? Désormais, OrderService ne crée pas lui-même le journaliseur. Il le reçoit tout prêt. C'est déjà de la DI, mais il y a une nuance : nous sommes toujours liés au type spécifique ConsoleLogger. Pour rendre le code flexible, il faut utiliser des traits.

\n\n

2. Utilisation des traits pour l'abstraction des dépendances

\n\n

Les traits en Rust sont l'équivalent des interfaces dans d'autres langages. Ils permettent de définir un contrat que les dépendances doivent respecter.

\n\n

Définir le trait Logger

\n\n
trait Logger {\n    fn log(&self, msg: &str);\n}\n\nstruct ConsoleLogger;\nimpl Logger for ConsoleLogger {\n    fn log(&self, msg: &str) {\n        println!("[Console] : {}", msg);\n    }\n}\n\nstruct FileLogger {\n    filename: String,\n}\nimpl Logger for FileLogger {\n    fn log(&self, msg: &str) {\n        // Simulation d'écriture dans un fichier\n        println!("[Fichier {}] : {}", self.filename, msg);\n    }\n}\n
\n\n

Injecter le trait via les génériques (generics)

\n\n
struct OrderService<L: Logger> {\n    logger: L, // Type générique implémentant le trait Logger\n}\n\nimpl<L: Logger> OrderService<L> {\n    fn new(logger: L) -> Self {\n        Self { logger }\n    }\n\n    fn place_order(&self, item: &str) {\n        self.logger.log(&format!("Commande : {}", item));\n    }\n}\n\nfn main() {\n    let console_logge                

Blogs

Book Recommendations