Introduction : pourquoi comparer Rust et Haskell ?
\n\nSi vous avez déjà jeté un œil sur Stack Overflow à la recherche d'une réponse à la question "What is the difference between traits in Rust and typeclasses in Haskell?", vous savez que le sujet est brûlant. Des milliers de vues, des dizaines de votes — la communauté des développeurs veut vraiment comprendre en quoi ces deux mécanismes diffèrent.
\n\nÀ première vue, ils se ressemblent comme des frères : les traits en Rust et les typeclasses en Haskell permettent tous deux de définir un comportement commun pour différents types. Mais si l'on creuse un peu, on découvre que la philosophie, l'implémentation et l'application pratique sont des univers différents.
\n\nDans cet article, nous analyserons les différences clés avec des exemples concrets de code en Rust (avec quelques incursions en Haskell pour le contraste), et à la fin vous aurez une compréhension claire : quand utiliser les traits, et où il est préférable de penser à une approche de type typeclass.
\n\nAperçu rapide : que sont les traits en Rust ?
\n\nLes traits en Rust sont un moyen de déclarer un ensemble de méthodes qui doivent être implémentées pour un type donné. C'est la base de la programmation générique (generics) et du polymorphisme en Rust.
\n\nExemple simple : imaginons que nous voulions que différents types sachent « dire bonjour ».
\n\ntrait Greeter {\n fn greet(&self) -> String;\n}\n\nstruct Person {\n name: String,\n}\n\nimpl Greeter for Person {\n fn greet(&self) -> String {\n format!("Bonjour, je m'appelle {} !", self.name)\n }\n}\n\nstruct Robot {\n model: String,\n}\n\nimpl Greeter for Robot {\n fn greet(&self) -> String {\n format!("Bip-boup, modèle : {}", self.model)\n }\n}\n\nfn say_hello(item: &impl Greeter) {\n println!("{}", item.greet());\n}\n\nfn main() {\n let person = Person { name: "Anna".into() };\n let robot = Robot { model: "R2-D2".into() };\n say_hello(&person);\n say_hello(&robot);\n}\n\nIci, Greeter est un trait. Les types Person et Robot l'implémentent à leur manière. La fonction say_hello accepte tout type implémentant Greeter. C'est ce qu'on appelle le polymorphisme via les traits.
Aperçu rapide : que sont les typeclasses en Haskell ?
\n\nUne typeclass en Haskell est une interface définissant un ensemble de fonctions qui peuvent être implémentées pour différents types. Cela vous semble familier, n'est-ce pas ?
\n\nVoici à quoi ressemblerait un exemple similaire en Haskell :
\n\nclass Greeter a where\n greet :: a -> String\n\ndata Person = Person { name :: String }\ndata Robot = Robot { model :: String }\n\ninstance Greeter Person where\n greet (Person n) = "Bonjour, je m'appelle " ++ n ++ " !"\n\ninstance Greeter Robot where\n greet (Robot m) = "Bip-boup, modèle : " ++ m\n\nsayHello :: Greeter a => a -> IO ()\nsayHello item = putStrLn (greet item)\n\nmain = do\n sayHello (Person "Anna")\n sayHello (Robot "R2-D2")\n\nLa syntaxe est différente, mais la logique est la même. Et pourtant, les différences entre les traits en Rust et les typeclasses en Haskell commencent justement ici.
\n\nDifférence clé n°1 : Monomorphisation vs Box (Box)
\n\nLa différence la plus fondamentale entre les traits et les typeclasses réside dans la façon dont le compilateur traite le code générique.
\n\nRust : monomorphisation
\n\nLorsque vous écrivez fn say_hello(item: &impl Greeter), le compilateur Rust crée une copie distincte de la fonction pour chaque type concret. C'est ce qu'on appelle la monomorphisation. Résultat : une abstraction à coût nul (zero-cost abstraction) — vous ne payez pas pour ce que vous n'utilisez pas. Mais la taille du code peut augmenter.
Haskell : box (par défaut)
\n\nEn Haskell, les typeclasses sont généralement implémentées via des dictionnaires de méthodes (vtable). Le compilateur transmet un paramètre implicite — une table avec des pointeurs vers les fonctions. C'est ce qu'on appelle le boxing. Vous payez pour la