Introduction : Deux mondes dans un seul langage
\n\nLorsque vous commencez à apprendre Rust, vous remarquez rapidement sa position unique. Ce langage ne vous impose pas une seule façon de penser. Contrairement à Java (où tout est objet) ou Haskell (où tout est fonction), Rust vous offre la liberté de choix. Mais avec cette liberté vient la responsabilité : quelle approche choisir – fonctionnelle ou orientée objet ?
\n\nDe nombreux développeurs venant à Rust depuis le monde Python ou C++ sont intuitivement attirés par la POO (programmation orientée objet). Ils cherchent des classes, de l'héritage et des patrons familiers. D'autres, inspirés par le monde Haskell ou Scala, essaient d'écrire en Rust exclusivement dans un style fonctionnel, en utilisant des monades, l'immuabilité et la composition.
\n\nLa vérité, comme toujours, se trouve au milieu. Rust est un langage multi-paradigme qui vous permet de prendre le meilleur des deux mondes. Dans cet article, nous allons voir comment utiliser efficacement l'approche fonctionnelle en Rust et quand appliquer les patrons orientés objet. Vous apprendrez à écrire un code qui non seulement fonctionne, mais qui ravit aussi par son élégance.
\n\nFondamentaux de la POO en Rust : Les traits au lieu des classes
\n\nMettons les choses au clair tout de suite. Il n'y a pas de classes en Rust au sens classique. Mais cela ne signifie pas que vous ne pouvez pas utiliser la POO. Au lieu des classes, on utilise ici les traits et les structures (structs). Ce sont eux qui permettent de réaliser l'encapsulation, le polymorphisme et même une certaine forme d'héritage.
\n\nStructures et méthodes
\n\nLes structures sont vos « objets ». Vous définissez les données, puis à l'aide de impl vous y ajoutez des méthodes :
struct Player {\n name: String,\n health: i32,\n level: u32,\n}\n\nimpl Player {\n // Méthode constructeur\n fn new(name: &str) -> Self {\n Player {\n name: name.to_string(),\n health: 100,\n level: 1,\n }\n }\n\n // Méthode modifiant l'état\n fn take_damage(&mut self, damage: i32) {\n self.health = (self.health - damage).max(0);\n }\n\n // Getter (méthode d'accès)\n fn is_alive(&self) -> bool {\n self.health > 0\n }\n}\n\n\nC'est du pur style POO : nous avons un état (les champs de la structure) et un comportement (les méthodes). Rust assure l'encapsulation via le contrôle de visibilité (modificateur pub).
Polymorphisme via les traits
\n\nAu lieu de l'héritage de classes, Rust utilise le polymorphisme via les traits. C'est plus puissant et plus sûr que l'héritage classique, car vous n'êtes pas confronté au « problème du losange » ou à l'héritage accidentel de comportements inutiles.
\n\ntrait Attackable {\n fn attack(&self, target: &mut Player);\n}\n\nstruct Swordsman {\n damage: i32,\n}\n\nimpl Attackable for Swordsman {\n fn attack(&self, target: &mut Player) {\n target.take_damage(self.damage);\n println!("L'épéiste a infligé {} dégâts !", self.damage);\n }\n}\n\nstruct Mage {\n spell_power: i32,\n mana_cost: i32,\n}\n\nimpl Attackable for Mage {\n fn attack(&self, target: &mut Player) {\n target.take_damage(self.spell_power * 2);\n println!("Le mage a lancé une boule de feu d'une puissance de {} !", self.spell_power * 2);\n }\n}\n\n\nVous pouvez maintenant travailler avec n'importe quel objet implémentant le trait Attackable, sans vous soucier de son type concret :
fn perform_attack(attacker: &impl Attackable, target: &mut Player) {\n attacker.attack(target);\n}\n\n\nC'est ce qu'on appelle le « duck typing » sous stéroïdes ou polymorphisme statique. Rust vérifie tout à la compilation, vous n'aurez donc pas d'erreur à l'exécution.
\n\nApproche fonctionnelle : Itérateurs et fermetures
\n\nPassons maintenant