coldwa.st
Tous les guidesProgrammationWebDonnéesOutilsBases de donnéesHaskellConceptsCabal & buildsChaîne d’outilsCompilateurPerformanceÉditeur & HLS

Haskell · Rust · langages

Haskell vs Rust

Par ColdwastMis a jour le 20 sept. 20269 min de lecture#haskell#rust
Lignes de code source colore sur un ecran sombre, coloration syntaxique verte et jaune sur fond noir, photographie en leger angle
Du code source sur un ecran sombre. Haskell et Rust exigent tous deux de satisfaire le compilateur avant toute execution, mais ils verifient des choses fondamentalement differentes.

Haskell et Rust sont tous deux a typage statique, tous deux compiles, et tous deux attirent des developpeurs soucieux de la correction. Cette ressemblance de surface cache une divergence profonde sur ce que chaque langage considere comme la source principale de bugs, et donc sur ce qu'il vous oblige a gerer a la compilation.

Haskell considere les effets de bord comme le danger principal. Une fonction qui lit un fichier ou modifie une variable porte cette information dans son type. Les fonctions pures, qui prennent des valeurs et en retournent sans autre interaction, sont le mode par defaut, et l'impurete est l'exception que vous devez declarer.

Rust considere les erreurs d'utilisation de la memoire comme le danger principal. Ses regles d'ownership et d'emprunt garantissent a la compilation que deux parties d'un programme ne detiennent jamais simultanement une reference mutable vers les memes donnees. Les courses aux donnees, les use-after-free et les double-free sont exclus par le verificateur de types, pas par un ramasse-miettes.

Ni l'un ni l'autre n'est un sur-ensemble de l'autre. La question n'est pas lequel est meilleur, mais quel probleme vous resolvez.

Systemes de types : purete vs ownership

Le systeme de types de Haskell descend de Hindley-Milner. Il supporte le polymorphisme parametrique, les classes de types (proches des traits Rust), les types de kind superieur et une separation entre code pur et effectful imposee par la monade IO. L'inference de types est omnipresente : vous pouvez ecrire des programmes entiers sans une seule annotation et le compilateur inferera chaque type.

Le systeme de types de Rust est affine. Chaque valeur a exactement un proprietaire ; quand l'ownership est transferee, la liaison d'origine n'est plus utilisable. L'emprunt (partage &T ou exclusif &mut T) permet de referencer des donnees sans en prendre l'ownership, sous la regle que vous pouvez avoir soit une reference mutable soit un nombre quelconque de references partagees, jamais les deux. Les lifetimes, notees 'a, indiquent au compilateur combien de temps une reference est valide.

En pratique, la difference se manifeste dans ce contre quoi vous vous battez avec le compilateur. En Haskell, vous luttez contre le verificateur de types quand vos signatures de fonctions ne composent pas. En Rust, vous luttez contre le borrow checker quand votre flux de donnees viole les regles d'ownership.

Gestion de la memoire

Haskell utilise un ramasse-miettes par tracage (GC). Le runtime GHC inclut un GC generationnel, par copie, qui s'execute de facon concurrente sur une capability separee. L'allocation est rapide (incrementer un pointeur dans une nursery), et les valeurs a courte duree de vie sont peu couteuses. Le cout apparait sous forme de pauses GC, que le flag -N de GHC et le dimensionnement de la nursery peuvent reduire mais pas eliminer.

Rust n'utilise aucun ramasse-miettes. La memoire est liberee de facon deterministe quand la variable proprietaire sort de la portee. Box, Rc, Arc et les collections standard gerent la memoire heap, mais toutes liberent leur contenu a un point connu. Le compromis est que vous devez satisfaire le borrow checker, ce qui est un cout a la compilation en effort du developpeur, pas un cout a l'execution en pauses.

Gros plan d'un ecran sombre affichant des lignes de code HTML et CSS en coloration syntaxique bleue, verte et orange, avec les numeros de ligne visibles sur la marge gauche
Un editeur de code avec de la coloration syntaxique. Haskell delegue les decisions memoire a un ramasse-miettes ; Rust les resout a la compilation via les regles d'ownership.

Caracteristiques de performance

Rust compile en code natif sans surcharge d'execution au-dela de ce que le programmeur demande explicitement. Le compilateur (rustc, appuye sur LLVM) produit des binaires comparables en vitesse a C et C++. La bibliotheque standard de Rust evite les allocations cachees : quand une fonction alloue, elle le dit dans sa signature en prenant ou retournant un type possede.

Haskell compile en code natif via GHC, mais l'evaluation paresseuse rend le raisonnement sur les performances plus difficile. L'evaluation paresseuse signifie que les expressions ne sont pas calculees avant que leur valeur soit demandee. Cela permet des abstractions elegantes (listes infinies, pipelines composables) mais peut aussi construire des chaines de calculs non evalues appelees thunks qui consomment de la memoire de facon inattendue. Les annotations strictes (!, BangPatterns, seq) corrigent cela, mais savoir ou les placer est un savoir-faire acquis.

Dans des benchmarks comme le Computer Language Benchmarks Game, les programmes Rust sont typiquement 2 a 10 fois plus rapides que les programmes Haskell equivalents et utilisent significativement moins de memoire. L'ecart se reduit quand le code Haskell est soigneusement optimise, mais l'effort de base requis est plus eleve.

Concurrence et parallelisme

Le runtime de Haskell fournit des threads verts legers (lances par forkIO), la memoire transactionnelle logicielle (STM), et la bibliotheque async pour la concurrence structuree. Comme le code pur n'a pas d'effets de bord, il est trivialement sur a executer en parallele : le compilateur peut meme paralleliser les calculs purs automatiquement avec par et pseq.

Rust previent les courses aux donnees a la compilation via l'ownership. Send et Sync sont des traits marqueurs que le compilateur verifie automatiquement : un type qui n'est pas sur a envoyer entre threads ne compilera tout simplement pas dans un contexte qui tente de le faire. Le runtime tokio fournit un executeur async ; rayon fournit le parallelisme de donnees. Les deux sont des bibliotheques, pas des fonctionnalites du langage.

La difference pratique : Haskell rend la concurrence facile a ecrire mais plus difficile a predire (les thunks paresseux peuvent migrer entre threads). Rust rend la concurrence plus difficile a ecrire (le borrow checker est strict sur l'etat mutable partage) mais plus facile a predire (vous savez exactement ce qui s'execute ou).

Ecosysteme et outillage

Le gestionnaire de paquets de Rust est cargo. Il gere la compilation, les tests, le benchmarking, la documentation et la resolution de dependances. Le registre, crates.io, heberge plus de 150 000 crates en 2026. rustfmt formate le code, clippy le verifie, et les deux sont livres avec la chaine d'outils.

Les principaux outils de build de Haskell sont cabal-install et stack, qui coexistent de facon un peu maladroite. Hackage heberge environ 17 000 paquets. ghcup gere les versions de GHC et de la chaine d'outils. Le Haskell Language Server (HLS) fournit les fonctionnalites IDE. L'ecosysteme est plus petit mais profond dans des domaines specifiques : parsing (megaparsec, attoparsec), compilateurs, methodes formelles et modelisation financiere.

Courbe d'apprentissage

Les deux langages sont difficiles a apprendre par rapport a Python ou JavaScript. Ils sont difficiles de facons differentes.

Haskell exige d'apprendre un nouveau paradigme de programmation. Si vous venez d'un background imperatif, les monades, les foncteurs, les applicatifs, les classes de types et l'evaluation paresseuse sont des concepts reellement nouveaux. La syntaxe est inhabituelle, et les messages d'erreur de GHC peuvent etre cryptiques tant que vous n'avez pas appris a lire les rapports de discordance de types.

Rust exige d'apprendre un nouveau modele memoire. Les concepts d'ownership, d'emprunt, de lifetimes et la distinction entre Copy et Clone n'ont pas d'equivalent direct dans la plupart des langages. Le borrow checker rejette du code qui compilerait sans souci en C++, et comprendre pourquoi est la partie difficile.

Quand choisir lequel

Choisissez Haskell quand la correction de la logique compte plus que le controle du materiel. Les compilateurs, interpreteurs, parseurs, langages specifiques a un domaine, moteurs de regles financieres et prototypes de recherche sont de bons candidats. Si votre probleme s'exprime le mieux comme une composition de transformations pures, Haskell vous permet de l'ecrire directement.

Choisissez Rust quand vous avez besoin de performances previsibles sans ramasse-miettes. La programmation systeme, le firmware embarque, les moteurs de jeu, les modules WebAssembly, les services reseau sous contrainte stricte de latence et les outils en ligne de commande qui doivent demarrer instantanement sont de bons candidats. Si votre programme ne doit pas faire de pause, Rust l'impose structurellement.

Ils se chevauchent dans les backends web (Haskell a Servant et Yesod ; Rust a Actix-web et Axum), les outils CLI et le traitement de donnees. Dans ces domaines, le choix depend de ce que vous valorisez : le prototypage rapide avec des abstractions fortes (Haskell) ou le debit brut avec un controle explicite (Rust).

Ce qu'ils partagent

Les deux langages utilisent des types de donnees algebriques (data en Haskell, enum en Rust). Les deux ont du pattern matching exhaustif impose par le compilateur. Les deux supportent les generiques (polymorphisme parametrique en Haskell, parametres de types generiques en Rust). Les deux ont des traits ou des classes de types comme mecanisme de polymorphisme ad-hoc. Les deux produisent des binaires autonomes sans interpreteur.

Et les deux recompensent le programmeur qui lit attentivement les messages d'erreur du compilateur, parce que dans les deux cas, le compilateur a generalement raison.

Lectures connexes : comment lire une signature de type Haskell, qu'est-ce que WebAssembly.