Traits in Rust and Typeclasses in Haskell: A Battle of Abstractions

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Introduction: Why Compare Rust and Haskell?

\n\n

If you've ever looked at Stack Overflow for an answer to the question "What is the difference between traits in Rust and typeclasses in Haskell?", you know it's a hot topic. Thousands of views, dozens of votes — the developer community really wants to understand how these two mechanisms differ.

\n\n

At first glance, they look like brothers: both traits in Rust and typeclasses in Haskell allow you to define common behavior for different types. But if you dig deeper, it turns out that their philosophy, implementation, and practical application are different universes.

\n\n

In this article, we'll break down the key differences using real code examples in Rust (with some Haskell for contrast), and by the end, you'll have a clear understanding: when to use traits, and where it's better to think about a typeclass-like approach.

\n\n

Brief Overview: What are Traits in Rust?

\n\n

Traits in Rust are a way to declare a set of methods that must be implemented for a given type. This is the foundation of generic programming (generics) and polymorphism in Rust.

\n\n

A simple example: imagine we want different types to be able to "greet".

\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!("Hello, my name is {}!", self.name)\n    }\n}\n\nstruct Robot {\n    model: String,\n}\n\nimpl Greeter for Robot {\n    fn greet(&self) -> String {\n        format!("Beep-boop, model: {}", 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

Here, Greeter is a trait. The Person and Robot types implement it in their own way. The say_hello function accepts any type that implements Greeter. This is polymorphism through traits.

\n\n

Brief Overview: What are Typeclasses in Haskell?

\n\n

A Typeclass in Haskell is an interface that defines a set of functions that can be implemented for different types. Sounds familiar, right?

\n\n

Here's what a similar example would look like in 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) = "Hello, my name is " ++ n ++ "!"\n\ninstance Greeter Robot where\n    greet (Robot m) = "Beep-boop, model: " ++ 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

The syntax is different, but the logic is the same. And yet, the differences between traits in Rust and typeclasses in Haskell begin right here.

\n\n

Key Difference #1: Monomorphization vs Boxing (Box)

\n\n

The most fundamental difference between traits and typeclasses lies in how the compiler handles generic code.

\n\n

Rust: Monomorphization

\n\n

When you write fn say_hello(item: &impl Greeter), the Rust compiler creates a separate copy of the function for each concrete type. This is called monomorphization. The result: zero-cost abstraction — you don't pay for what you don't use. But the code size can grow.

\n\n

Haskell: Boxing (by default)

\n\n

In Haskell, typeclasses are usually implemented via method dictionaries (vtable). The compiler passes an implicit parameter — a table with pointers to functions. This is called boxing. You pay for the

Blogs

Book Recommendations