Traits en Rust et Typeclasses en Haskell : la bataille des abstractions

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 comparer Rust et Haskell ?

\n\n

Si 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\n

Dans 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\n

Aperçu rapide : que sont les traits en Rust ?

\n\n

Les 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\n

Exemple simple : imaginons que nous voulions que différents types sachent « dire bonjour ».

\n\n
trait 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\n

Ici, 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.

\n\n

Aperçu rapide : que sont les typeclasses en Haskell ?

\n\n

Une 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\n

Voici à quoi ressemblerait un exemple similaire en Haskell :

\n\n
class 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\n

La 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\n

Différence clé n°1 : Monomorphisation vs Box (Box)

\n\n

La 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\n

Rust : monomorphisation

\n\n

Lorsque 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.

\n\n

Haskell : box (par défaut)

\n\n

En 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

Blogs

Book Recommendations