Traits em Rust e Typeclasses em Haskell: a batalha das abstrações

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course

Introdução: por que comparar Rust e Haskell?

\n\n

Se você já deu uma olhada no Stack Overflow em busca de uma resposta para a pergunta "What is the difference between traits in Rust and typeclasses in Haskell?", então sabe: o assunto é polêmico. Milhares de visualizações, dezenas de votos — a comunidade de desenvolvedores realmente quer entender como esses dois mecanismos diferem.

\n\n

À primeira vista, eles são parecidos como irmãos: tanto traits em Rust quanto typeclasses em Haskell permitem definir um comportamento comum para diferentes tipos. Mas se você cavar mais fundo, descobre-se que a filosofia, a implementação e a aplicação prática deles são universos diferentes.

\n\n

Neste artigo, analisaremos as principais diferenças com exemplos reais de código em Rust (com alguns toques de Haskell para contraste), e no final você terá uma compreensão clara: quando usar traits e onde é melhor pensar em uma abordagem semelhante a typeclass.

\n\n

Breve visão geral: o que são traits em Rust?

\n\n

Traits em Rust são uma forma de declarar um conjunto de métodos que devem ser implementados para um determinado tipo. Esta é a base da programação genérica (generics) e do polimorfismo em Rust.

\n\n

Um exemplo simples: imagine que queremos que diferentes tipos saibam "cumprimentar".

\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!("Olá, meu nome é {}!", 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, modelo: {}", self.model)\n    }\n}\n\nfn say_hello(item: &impl Greeter) {\n    println!("{}", item.greet());\n}\n\nfn main() {\n    let person = Person { name: "Ana".into() };\n    let robot = Robot { model: "R2-D2".into() };\n    say_hello(&person);\n    say_hello(&robot);\n}
\n\n

Aqui, Greeter é uma trait. Os tipos Person e Robot a implementam de sua própria maneira. A função say_hello aceita qualquer tipo que implemente Greeter. Isso é o polimorfismo através de traits.

\n\n

Breve visão geral: o que são typeclasses em Haskell?

\n\n

Typeclass em Haskell é uma interface que define um conjunto de funções que podem ser implementadas para diferentes tipos. Parece familiar, certo?

\n\n

Aqui está como seria um exemplo análogo em 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) = "Olá, meu nome é " ++ n ++ "!"\n\ninstance Greeter Robot where\n    greet (Robot m) = "Beep-boop, modelo: " ++ m\n\nsayHello :: Greeter a => a -> IO ()\nsayHello item = putStrLn (greet item)\n\nmain = do\n    sayHello (Person "Ana")\n    sayHello (Robot "R2-D2")
\n\n

A sintaxe é diferente, mas a lógica é a mesma. E ainda assim, as diferenças entre traits em Rust e typeclasses em Haskell começam exatamente aqui.

\n\n

Diferença chave nº1: Monomorfização vs boxing (Box)

\n\n

A diferença mais fundamental entre traits e typeclasses está em como o compilador lida com código genérico.

\n\n

Rust: monomorfização

\n\n

Quando você escreve fn say_hello(item: &impl Greeter), o compilador Rust cria uma cópia separada da função para cada tipo específico. Isso é chamado de monomorfização. O resultado: abstração de custo zero (zero-cost abstraction) — você não paga pelo que não usa. Mas o tamanho do código pode aumentar.

\n\n

Haskell: boxing (por padrão)

\n\n

Em Haskell, typeclasses são geralmente implementadas através de dicionários de métodos (vtable). O compilador passa um parâmetro implícito — uma tabela com ponteiros para funções. Isso é chamado de boxing. Você paga pelo co

Blogs

Book Recommendations