Comparaison entre Ada et Rust à travers la résolution de problèmes AoC
(github.com/johnperry-math)- Compare les différences et caractéristiques apparues en résolvant des problèmes Advent of Code avec Ada et Rust
- Analyse les écarts entre la conception de ces deux langages, centrée sur la sécurité et la fiabilité, et leurs approches concrètes de l’écriture de programmes
- Les différences ressortent sous divers angles, comme la bibliothèque standard de chaque langage, les fonctionnalités intégrées, les écarts de performance et le style de gestion des erreurs
- Explique, à l’aide d’exemples de code concrets, des cas pratiques rencontrés lors de l’écriture et de l’exploitation du code autour de la modularité, des génériques, des boucles et de la gestion des erreurs
- Les différences d’expérience de développement sont particulièrement marquées par le typage statique, le traitement des tableaux et les interfaces de gestion des erreurs
Introduction et objectif
- Après avoir utilisé uniquement Ada pour résoudre les problèmes Advent of Code (ci-après AoC), l’auteur a commencé à écrire aussi des solutions en Rust et en Modula-2 à partir de 2023, ce qui lui a permis de les comparer directement
- En portant des solutions auparavant centrées sur Ada vers Rust, il a ressenti les différences structurelles entre les deux langages et leurs approches propres
- L’objectif est de clarifier les écarts d’usage réels du point de vue de la sécurité du code, de la fiabilité et de la conception du langage
Versions des langages utilisées pour la comparaison
- Ada 2022 (avec, selon les besoins, référence à certaines règles de Spark 2014)
- Rust 2021 (comparaison principale basée sur Rust 1.81.0)
Fonctionnalités exclues et critères de comparaison
- Les fonctionnalités emblématiques de chaque langage (= killer features) sont brièvement mentionnées dans le texte sous forme de commentaires
- Certaines fonctionnalités n’ont pas été traitées, selon l’expérience personnelle de l’auteur et les besoins concrets de chaque solution
- L’auteur cherche à écarter autant que possible les avis personnels pour se concentrer sur les caractéristiques principales
Contexte et point de vue de l’auteur
- En tant qu’utilisateur non natif d’Ada comme de Rust, l’auteur s’appuie sur une expérience des langages des années 1980 comme C/C++, Pascal et Modula-2
- En conséquence, son style de code peut différer des styles modernes ou idiomatiques
- Les implémentations ne sont pas forcément optimales et, selon le contexte, certaines solutions choisies peuvent être intuitives ou au contraire peu conventionnelles
Positionnement d’Ada et de Rust
- Ada reste un langage de développement système/embarqué extrêmement sûr et fiable, avec un fort accent sur la lisibilité du code
- Rust met en avant la sûreté mémoire et les atouts de la programmation système, et a été désigné pendant plusieurs années comme le « langage le plus apprécié » dans l’enquête développeurs de Stack Overflow
- Ada est un langage généraliste de haut niveau, offrant un spectre particulièrement adapté à la lecture et à la maintenance
- Rust vise le développement de programmes système de bas niveau, avec une culture de programmation sûre fondée sur la gestion explicite de la mémoire et les types d’erreur/option
Comparaison des caractéristiques de sécurité et de structure
-
Ada
- Norme ISO (spécification rigoureuse)
- Il est facile de définir des types adaptés aux caractéristiques du problème (plage, nombre de chiffres, etc.)
- Les indices de tableau ne sont pas forcément numériques
- Il existe une spécification encore plus stricte appelée Spark
-
Rust
- La spécification repose surtout sur la documentation officielle (Reference) et le compilateur
- Les déclarations de types dépendent des types machine (par ex.
f64,u32) - L’indexation de tableaux est naturellement limitée aux types numériques
Résumé principal du tableau des fonctionnalités / intégration
- Différences dans la prise en charge du contrôle des bornes des tableaux, des conteneurs génériques, de la concurrence, des boucles étiquetées et du pattern matching
- Ada repose sur les exceptions pour la gestion des erreurs, tandis que Rust privilégie une approche par valeur de retour via les types Result/Option
- Rust se distingue nettement par la prise en charge des macros, du pattern matching et d’une certaine pureté fonctionnelle
- Ada prend en charge la conception par contrat ainsi que la vérification à la compilation DBC (Design By Contract) dans Spark
- En matière de sûreté mémoire, Rust et Spark l’imposent, tandis qu’Ada autorise l’usage de pointeurs nuls
Comparaison des performances et du temps d’exécution
- Rust a généralement la réputation d’être rapide à l’exécution mais lent à compiler, tandis qu’Ada est vu à l’inverse comme rapide à compiler, avec des performances à l’exécution pouvant être un peu moindres selon les vérifications activées
- D’après les benchmarks, Rust a rencontré un dépassement sur le problème day24 à cause des limites du type f64, alors qu’Ada a pu utiliser des types de haut niveau comme digits 18, permettant une sélection automatique d’un type machine adapté, l’évitement du dépassement et d’excellentes performances
- Rust doit recourir à
f128non stable ou à une bibliothèque externe, alors qu’Ada peut prendre l’avantage simplement en spécifiant un type conforme aux capacités du compilateur
Traitement des fichiers et gestion des erreurs (Case Study 1)
Traitement des fichiers en Ada
- Utilisation de Ada.Text_IO par défaut
- Ouverture explicite du fichier, lecture ligne par ligne, traitement de plages souhaitées ou de lignes par position, de manière relativement intuitive
- En cas d’erreur, le traitement se fait par exceptions plutôt que via des messages explicites dans la signature de fonction, et la possibilité d’erreur n’apparaît pas dans cette signature
Traitement des fichiers en Rust
- Utilisation de std::fs::File et de BufReader
- À l’ouverture du fichier, retour d’un type Result, ce qui rend clairement visible la possibilité d’erreur
- Pas de prise en charge directe de l’indexation par caractère, traitement obligatoirement via des Iterator
- Les outils fonctionnels et itératifs comme map, filter, collect, sum sont centraux, avec prise en charge de diverses macros (par ex.
include_str!) - En déclarant explicitement les erreurs dans le type de retour, Rust assure une propagation claire des erreurs au niveau des fonctions
Modularité et génériques (Case Study 2)
Modularité en Ada
- Séparation claire entre spécification (interface) et implémentation sur la base des packages
- Renforcement de la modularité via des sous-packages et une combinaison de la syntaxe use/rename pour ajuster la lisibilité
- Prise en charge des generic dans les packages : généralisation de types, constantes ou sous-packages entiers
Modularité en Rust
- Organisation modulaire via le système mod/crate, avec distinction spécification/implémentation automatisée par le générateur de documentation
- Contrôle d’accès déclaratif avec pub/private
- Combinaison de use/as pour les imports et renommages
- Prise en charge intégrée des tests, permettant de déclarer directement des modules de test dans le code, de les compiler et de les exécuter automatiquement
Génériques
- Ada ne prend en charge les génériques qu’au niveau des packages/procédures (pas au niveau d’un type seul)
- Rust permet d’appliquer des génériques aux types eux-mêmes (approche de type template)
- Ada permet d’exprimer clairement des propriétés supplémentaires comme l’étendue d’un type via les types de plage et sous-types, tandis que Rust s’appuie sur des constantes d’instance
Comparaison des types énumérés (Case Study 3)
- Ada offre une déclaration concise tout en prenant automatiquement en charge les types discrets, l’ordre et leur utilisation dans les boucles/indices
- Les
enumde Rust ont une déclaration similaire, mais leur exploitation via le pattern matching et l’itération demande une approche plus explicite
Conclusion
- Ada offre un contrôle plus strict sur les types de spécification de haut niveau, la vérifiabilité et les contrôles à l’exécution
- Rust l’emporte en matière de confort de développement et de sécurité grâce au style de programmation fonctionnel, à la métaprogrammation par macros et à la gestion des erreurs assistée par le compilateur
- Dans la résolution de problèmes concrets, Ada montre ses forces en compatibilité avec l’ancien code et en maintenance, tandis que Rust bénéficie d’un écosystème d’outils moderne ainsi que d’avantages en sûreté et en parallélisme
1 commentaires
Réactions sur Hacker News
Explication des subranges en Nim
Documentation associée
Spécification Rust
char, donc l’indexation directe comme sur un tableau d’octets est simple. Rust a été conçu avec Unicode en tête, donc ses chaînes sont fondamentalement du texte encodé en UTF-8, c’est-à-dire du vrai « texte ». Ada permet donc une indexation aléatoire comme un tableau, alors qu’en Rust la notion de chaîne est différente, même s’il reste possible de passer par un tableau d’octets[u8]ou la méthodestr::as_bytesVecMap) qui est rapide pour peu d’éléments, mais au-delà d’un certain seuil une autre structure devient plus efficace. J’aimerais savoir où se situe concrètement le point où le work stealing devient avantageuxSite de Prunt
Prunt sur GitHub
Commentaire HN associé
SIDE_LENGTH, alors il faut ajouter une fonction qui le renvoie », mais une déclaration de constante commepub const SIDE_LENGTH: usize = ROW_LENGTH;serait une solution plus directe qu’une fonctionBirdSpecies, ce qui rendeggs[Robin]eteggs[Seagull]valides, tandis queeggs[5]est interdit. En Rust aussi, on peut créer la structure de données voulue (par exemple en implémentantIndex<BirdSpecies>), de sorte queeggs[Robin]fonctionne maiseggs[5]provoque une erreur. En revanche, Rust ne le prend pas directement en charge comme « tableau » au niveau du langage. Ce type d’indexation devient particulièrement intéressant quand, comme en Ada, on peut déclarer un type défini par l’utilisateur comme un entier restreint à un sous-ensemble. En Rust, on ne peut pas encore créer de purs types utilisateurs de ce genre, comme des entiers à plage bornée (il n’existe en interne que des types commeNonZeroI16). Ce serait excellent si Rust allait jusque-làsubtype) est, structurellement, un concept totalement différent d’un dictionnaire. Le langage peut aller jusqu’à restreindre les catégories mêmes de valeurs autorisées comme indices de tableau.