Haskell · Rust · linguagens
Haskell vs Rust
Haskell e Rust sao ambos estaticamente tipados, ambos compilados, e ambos atraem programadores preocupados com a correcao. Esta semelhanca superficial esconde uma divergencia profunda naquilo que cada linguagem considera a fonte principal de bugs, e portanto naquilo que vos obriga a tratar em tempo de compilacao.
Haskell considera os efeitos colaterais como o perigo principal. Uma funcao que le um ficheiro ou modifica uma variavel carrega essa informacao no seu tipo. As funcoes puras, que recebem valores e devolvem valores sem outra interacao, sao o modo por defeito, e a impureza e a excecao que se deve declarar.
Rust considera os erros de utilizacao da memoria como o perigo principal. As suas regras de ownership e borrowing garantem em tempo de compilacao que duas partes de um programa nunca detenham simultaneamente uma referencia mutavel para os mesmos dados. As data races, os use-after-free e os double-free sao excluidos pelo verificador de tipos, nao por um garbage collector.
Nenhum e um superconjunto do outro. A questao nao e qual e melhor, mas que problema se esta a resolver.
Sistemas de tipos: pureza vs ownership
O sistema de tipos de Haskell descende de Hindley-Milner. Suporta polimorfismo parametrico, classes de tipos (semelhantes aos traits de Rust), tipos de kind superior e uma separacao entre codigo puro e com efeitos imposta pela monada IO. A inferencia de tipos e omnipresente: podem escrever-se programas inteiros sem uma unica anotacao e o compilador inferira cada tipo.
O sistema de tipos de Rust e afim. Cada valor tem exatamente um proprietario; quando o ownership e transferido, o binding original deixa de ser utilizavel. O borrowing (partilhado &T ou exclusivo &mut T) permite referenciar dados sem tomar o ownership, com a regra de que se pode ter uma referencia mutavel ou um numero qualquer de referencias partilhadas, nunca ambas. Os lifetimes, escritos como 'a, indicam ao compilador durante quanto tempo uma referencia e valida.
Na pratica, a diferenca manifesta-se naquilo sobre o que se discute com o compilador. Em Haskell luta-se contra o verificador de tipos quando as assinaturas das funcoes nao compoem. Em Rust luta-se contra o borrow checker quando o fluxo de dados viola as regras de ownership.
Gestao da memoria
Haskell usa um garbage collector por tracagem (GC). O runtime GHC inclui um GC geracional, por copia, que corre de forma concorrente numa capability separada. A alocacao e rapida (incrementar um ponteiro numa nursery), e os valores de curta duracao sao baratos. O custo manifesta-se como pausas do GC, que o flag -N do GHC e o dimensionamento da nursery podem reduzir mas nao eliminar.
Rust nao usa qualquer garbage collector. A memoria e libertada de forma deterministica quando a variavel proprietaria sai do scope. Box, Rc, Arc e as colecoes standard gerem a memoria heap, mas todas libertam o conteudo num ponto conhecido. O compromisso e que se deve satisfazer o borrow checker, o que e um custo em tempo de compilacao em esforco do programador, nao um custo em tempo de execucao em pausas.
Caracteristicas de desempenho
Rust compila para codigo nativo sem sobrecarga de runtime para alem do que o programador solicita explicitamente. O compilador (rustc, apoiado no LLVM) produz binarios comparaveis em velocidade a C e C++. A biblioteca standard de Rust evita alocacoes ocultas: quando uma funcao aloca, di-lo na sua assinatura ao receber ou devolver um tipo possuido.
Haskell compila para codigo nativo atraves do GHC, mas a avaliacao preguicosa torna o raciocinio sobre desempenho mais dificil. A avaliacao preguicosa significa que as expressoes nao sao calculadas ate que o seu valor seja solicitado. Isto permite abstracoes elegantes (listas infinitas, pipelines componveis) mas pode tambem construir cadeias de calculos nao avaliados chamados thunks que consomem memoria de forma inesperada. As anotacoes strict (!, BangPatterns, seq) corrigem isto, mas saber onde coloca-las e uma competencia adquirida.
Em benchmarks como o Computer Language Benchmarks Game, os programas Rust sao tipicamente 2 a 10 vezes mais rapidos que os programas Haskell equivalentes e usam significativamente menos memoria. A diferenca reduz-se quando o codigo Haskell e cuidadosamente otimizado, mas o esforco de base necessario e superior.
Concorrencia e paralelismo
O runtime de Haskell fornece threads verdes leves (lancados por forkIO), memoria transacional por software (STM) e a biblioteca async para concorrencia estruturada. Como o codigo puro nao tem efeitos colaterais, e trivialmente seguro executa-lo em paralelo: o compilador pode ate paralelizar automaticamente calculos puros com par e pseq.
Rust previne as data races em tempo de compilacao atraves do ownership. Send e Sync sao traits marcadores que o compilador verifica automaticamente: um tipo que nao e seguro enviar entre threads simplesmente nao compilara num contexto que tente faze-lo. O runtime tokio fornece um executor async; rayon fornece paralelismo de dados. Ambos sao bibliotecas, nao funcionalidades da linguagem.
A diferenca pratica: Haskell torna a concorrencia facil de escrever mas mais dificil de prever (os thunks preguicosos podem migrar entre threads). Rust torna a concorrencia mais dificil de escrever (o borrow checker e rigoroso sobre estado mutavel partilhado) mas mais facil de prever (sabe-se exatamente o que corre onde).
Ecossistema e ferramentas
O gestor de pacotes de Rust e o cargo. Gere compilacao, testes, benchmarking, documentacao e resolucao de dependencias. O registo, crates.io, alberga mais de 150.000 crates em 2026. rustfmt formata o codigo, clippy analisa-o, e ambos sao distribuidos com a toolchain.
As principais ferramentas de build de Haskell sao cabal-install e stack, que coexistem de forma algo desajeitada. O Hackage alberga cerca de 17.000 pacotes. O ghcup gere as versoes do GHC e da toolchain. O Haskell Language Server (HLS) fornece funcionalidades IDE. O ecossistema e mais pequeno mas profundo em dominios especificos: parsing (megaparsec, attoparsec), compiladores, metodos formais e modelacao financeira.
Curva de aprendizagem
Ambas as linguagens sao dificeis de aprender comparadas com Python ou JavaScript. Sao dificeis de formas diferentes.
Haskell exige aprender um novo paradigma de programacao. Se vem de um background imperativo, monadas, functores, aplicativos, classes de tipos e avaliacao preguicosa sao conceitos genuinamente novos. A sintaxe e incomum, e as mensagens de erro do GHC podem ser cripticas ate se aprender a ler os relatorios de discordancia de tipos.
Rust exige aprender um novo modelo de memoria. Os conceitos de ownership, borrowing, lifetimes e a distincao entre Copy e Clone nao tem equivalente direto na maioria das linguagens. O borrow checker rejeita codigo que compilaria sem problema em C++, e compreender porque e a parte dificil.
Quando escolher qual
Escolha Haskell quando a correcao da logica importa mais que o controlo sobre o hardware. Compiladores, interpretes, parsers, linguagens especificas de dominio, motores de regras financeiras e prototipos de investigacao sao bons candidatos. Se o problema se expressa melhor como composicao de transformacoes puras, Haskell permite escreve-lo diretamente.
Escolha Rust quando precisa de desempenho previsivel sem garbage collector. Programacao de sistemas, firmware embebido, motores de jogo, modulos WebAssembly, servicos de rede com orcamentos rigorosos de latencia e ferramentas de linha de comandos que devem iniciar instantaneamente sao bons candidatos. Se o programa nao deve fazer pausas, Rust impoe-no estruturalmente.
Sobrepoe-se em backends web (Haskell tem Servant e Yesod; Rust tem Actix-web e Axum), ferramentas CLI e processamento de dados. Nestas areas a escolha depende de se valoriza a prototipagem rapida com abstracoes fortes (Haskell) ou o throughput bruto com controlo explicito (Rust).
O que partilham
Ambas as linguagens usam tipos de dados algebricos (data em Haskell, enum em Rust). Ambas tem pattern matching exaustivo imposto pelo compilador. Ambas suportam genericos (polimorfismo parametrico em Haskell, parametros de tipo generico em Rust). Ambas tem traits ou classes de tipos como mecanismo para polimorfismo ad-hoc. Ambas produzem binarios autonomos sem interprete.
E ambas recompensam o programador que le atentamente as mensagens de erro do compilador, porque em ambos os casos o compilador tem geralmente razao.
Leituras relacionadas: o que e WebAssembly.