Réflexions sur Go vs. Rust vs. Zig
(sinclairtarget.com)- En se concentrant sur les différences de philosophie et de valeurs entre les trois langages, l’article compare les problèmes que chacun cherche à résoudre
- Go est présenté comme un langage qui privilégie la simplicité et la stabilité, en réduisant les fonctionnalités au minimum pour faciliter la collaboration et la maintenance
- Rust vise à la fois la sécurité et les performances, et garantit la sécurité mémoire grâce à un système de types complexe et à une structure fondée sur les traits
- Zig est décrit comme un langage expérimental qui donne un contrôle total au développeur grâce à la gestion manuelle de la mémoire et à une conception orientée données
- Les approches contrastées des trois langages révèlent le système de valeurs mis en œuvre par un langage de programmation, et le critère de choix devient la philosophie à laquelle le développeur adhère
Angle de comparaison entre les langages
- L’auteur cherche à comprendre le système de valeurs propre à chaque langage non pas à travers celui utilisé au travail, mais via l’expérimentation de nouveaux langages
- Il souligne qu’au-delà d’une simple comparaison de listes de fonctionnalités, l’essentiel est de voir quels compromis le langage a choisis
- Go, Rust et Zig se recoupent largement sur le plan fonctionnel, mais les valeurs privilégiées par leurs concepteurs diffèrent
- Comprendre la philosophie de chaque langage permet de juger dans quels environnements et pour quels objectifs il est adapté
Go — un langage centré sur la simplicité et la collaboration
- Go se distingue par son minimalisme, avec cette caractéristique qu’« on peut garder l’ensemble du langage en tête »
- Les génériques n’ont été ajoutés qu’au bout de 12 ans, et des fonctionnalités comme les tagged unions ou le sucre syntaxique pour la gestion des erreurs n’existent toujours pas
- L’ajout de fonctionnalités se fait avec une extrême prudence, ce qui entraîne beaucoup de code boilerplate, mais renforce la stabilité et la lisibilité du langage
- Les slices de Go couvrent des fonctions que remplissent
Vec<T>en Rust ouArrayListen Zig, tandis que l’emplacement mémoire est géré automatiquement à l’exécution - Le langage est né d’un mécontentement face à la complexité de C++ et à la lenteur de compilation, avec pour objectif une compilation simple et rapide
- Il accorde une grande importance à l’efficacité de la collaboration en environnement d’entreprise, en privilégiant un code clair et cohérent plutôt que des fonctionnalités complexes
Rust — complexe, mais puissant en sécurité et en performances
- Rust revendique les « abstractions à coût nul » et se présente comme un langage maximaliste combinant de nombreux concepts
- Si son apprentissage est difficile, c’est parce que sa densité conceptuelle est élevée, avec un système de types complexe et une structure fondée sur les traits
- L’objectif central de Rust est de concilier performances et sécurité mémoire
- Il effectue des vérifications à la compilation pour empêcher les UB (Undefined Behavior)
- Il bloque les comportements imprévisibles causés par des références de pointeurs invalides ou des doubles libérations, entre autres
- Pour permettre au compilateur de comprendre le comportement du code à l’exécution, le développeur doit définir explicitement les types et les traits
- Grâce à cette structure, la fiabilité du code écrit par autrui est élevée, et l’écosystème de bibliothèques reste particulièrement dynamique
Zig — contrôle total et conception orientée données
- Zig est le plus récent des trois langages ; il en est à la version 0.14 et sa bibliothèque standard est presque dépourvue de documentation
- Il adopte une gestion manuelle de la mémoire, ce qui oblige le développeur à appeler lui-même
alloc()et à choisir un allocator - Contrairement à Rust ou Go, il permet de créer facilement des variables globales et interrompt le programme à l’exécution lorsqu’un « illegal behavior » est détecté
- Il propose quatre modes de release sélectionnables à la compilation pour ajuster l’équilibre entre performances et stabilité
- Les fonctionnalités de la programmation orientée objet (OOP) sont volontairement exclues
- Il n’y a ni champs privés ni dispatch dynamique, et même
std.mem.Allocatorn’est pas implémenté comme une interface - À la place, le langage vise une conception orientée données (data-oriented design)
- Il n’y a ni champs privés ni dispatch dynamique, et même
- Pour la gestion mémoire également, au lieu d’un contrôle fin objet par objet à la manière de RAII, il recommande une structure consistant à allouer et libérer périodiquement de gros blocs de mémoire
- Zig est décrit comme un langage au tempérament libre et anti-establishment, qui écarte la pensée OOP et pousse au maximum le contrôle exercé par le développeur
- L’équipe se concentre actuellement sur la réécriture de toutes les dépendances, et la version stable (1.0) n’a pas encore de date fixée
Conclusion — les différences de valeurs révélées par les langages
- Go met au centre la collaboration et la simplicité, Rust la sécurité et les performances, et Zig la liberté et le contrôle
- Les différences entre les trois ne relèvent pas d’une simple comparaison de fonctionnalités, mais reflètent un choix philosophique sur le développement logiciel
- Les développeurs finissent par choisir un langage en fonction des valeurs auxquelles ils adhèrent
1 commentaires
Réactions sur Hacker News
Il n’est pas difficile de créer une variable globale mutable en Rust
il faut simplement utiliser
unsafeou un smart pointer fournissant de la synchronisationRust est réentrant par défaut et garantit la sûreté des threads à la compilation
si l’on ne se soucie pas de la sûreté statique vis-à-vis des threads, on peut faire aussi facilement qu’en Zig ou en C
la différence, c’est que Rust fournit davantage d’outils de garantie sur le comportement du code à l’exécution
quand je retourne dans d’autres langages et que je vois ça utilisé sans hésitation, ça me paraît complètement fou du point de vue de la sécurité
mais quand ces « petites choses simples » s’accumulent, elles cessent d’être simples
Rust a déjà franchi cette ligne, et ce n’est désormais plus du tout trivial
si oui, ce serait plus séduisant que le C
j’aimerais aussi savoir comment on gère les cas où deux variables doivent toujours être verrouillées ensemble
en déboguant, on finit toujours par découvrir que le problème venait de là
À propos du billet qui critique la densité conceptuelle de Rust, je pense qu’en pratique on peut être productif en n’en connaissant que 5 %
j’utilise Rust depuis plus de 12 ans et je n’ai jamais eu besoin de quelque chose comme
#[fundamental]Rust permet aussi l’arena allocation, et la notion d’allocator existe bien
il y a simplement un allocator par défaut, et on utilise généralement des allocations explicites sur le tas comme
Box::newon peut créer une globale mutable comme
static FOO: Mutex<T> = Mutex::new(...), et un mutex est nécessaire pour la sécurité mémoirele système de types de Rust est conçu pour garantir non seulement la sécurité mémoire, mais aussi la sûreté sémantique du code
en C, il y a moins de cette complexité
la complexité reste donc un vrai sujet
le sujet n’est pas seulement de savoir si c’est possible, mais la différence de style de programmation par défaut
la Zig Software Foundation a aussi déjà mal cité des propos d’Asahi Lina sur Rust
cette attitude marketing de Zig consistant à rabaisser les autres langages ne plaît pas vraiment
Ce qui plaît dans Zig, c’est que le langage permet de gérer les pannes mémoire de façon élégante
toute allocation est supposée pouvoir échouer, et il faut la traiter explicitement
l’espace de pile n’est pas traité de manière magique non plus, et le compilateur analyse le graphe d’appels pour en déduire la taille maximale
dans l’embarqué, cette conception centrée sur les ressources est indispensable
ce n’est pas quelque chose qu’un langage peut résoudre à lui seul
au fond, on reste face au même problème de gestion mémoire manuelle
dans ce cas, je pense qu’il vaut mieux utiliser un langage à GC
simplement, comme la bibliothèque standard de Rust utilise un panic en cas d’OOM, il existe un écosystème séparé pour le développement embarqué en environnement no-std
Les slices de Go sont différentes des
Vec<T>de Rustappend()renvoie une nouvelle slice, qui peut ou non partager la mémoire existanteil n’y a pas de moyen de réduire la mémoire, et si l’on écrit seulement
append(s, ...), on ignore la nouvelle sliceGo a une attitude de type « fais ce que je te dis », alors que Rust dit plutôt « vérifie que tu as bien fait ce que je t’ai dit »
autrement dit, Go accepte davantage d’erreurs au nom de la simplicité, tandis que Rust choisit de réduire les erreurs même si cela rend les choses plus complexes
de plus, écrire simplement
append(s, ...)produit une erreur de compilation, donc l’affirmation d’origine est légèrement inexacteGo est un langage qui évalue avec prudence l’augmentation de complexité à chaque ajout de fonctionnalité
append(s, …)ne compile même pas, je pense qu’un débutant ne peut pas faire cette erreurprobablement parce qu’on a rarement besoin de passer directement une liste extensible
bien souvent, les gens sont surtout surpris parce qu’ils ne l’ont pas lue
Je pense qu’il est difficile, en pratique, d’attraper l’UB (Undefined Behavior) du C/C++ avec des vérifications à l’exécution
Android a bien appliqué des sanitizers à tous les commits, mais ce n’est qu’après le passage à Rust que les exploits ont diminué
J’ai aimé que l’article de comparaison des langages traite honnêtement des forces et faiblesses de chacun
dommage que Raku n’ait pas été mentionné
à mon avis, si C–Zig–C++–Rust–Go forment un continuum des langages bas niveau, alors le côté haut niveau se prolonge en Julia–R–Python–Lua–JS–PHP–Raku–WL
la définition de grammaires est prise en charge au niveau du langage, ce qui facilite les DSL et l’analyse de logs
basé sur une VM, il est moins performant, mais il est bien adapté pour exprimer directement la structure du problème
en tant que successeur de Perl, il vise un langage souple et cohérent
Penser qu’en Rust, lorsqu’une fonction renvoie un pointeur, cela provoque automatiquement une allocation sur le tas est une erreur
les variables locales vivent sur la pile et disparaissent au retour, donc le pointeur devient invalide
en mode sûr, Rust ne permet pas de déréférencer de tels pointeurs, et en mode unsafe, c’est au développeur de garantir leur validité
il semble probable que
Box::newait été pris à tort pour une « allocation implicite »soit le concept a été mal compris, soit il y a une volonté de tromper
Le plus grand atout de Go est son modèle de concurrence simple
grâce aux goroutines, il est facile d’écrire du code parallèle
retrouver les implémentations d’interfaces est difficile, mais la lisibilité est élevée, ce qui aide le travail en équipe
il n’y a pas de colored function, la communication par canaux est simple, et l’on peut écrire rapidement du code concurrent correct
article associé : Structured Concurrency or Go Statement Considered Harmful
std.Iode Zig ressemble au modèle de concurrence de Gole mot-clé
gocorrespond àstd.Io.async, les canaux àstd.Io.Queue, etselectàstd.Io.selectCe que je voudrais, c’est un langage combinant la simplicité de Go avec la gestion des résultats/erreurs/énumérations de Rust et de meilleurs génériques
on a vu OCaml, D, Swift, Nim, Crystal, etc., mais aucun n’a encore dominé le marché
à la place, Gleam mérite un coup d’œil
j’espère qu’une amélioration viendra résoudre ce type de problème récurrent
les génériques resteront sans doute un défi difficile
J’ai apprécié le ton général du texte, qui laissait sentir l’enthousiasme et la curiosité d’un développeur débutant
l’absence initiale de génériques en Go ne relevait pas d’un simple minimalisme, mais du résultat d’une vraie réflexion sur les compromis
les lifetimes de Rust ont été le principal obstacle pour beaucoup de gens, et l’innovation du langage tient à la combinaison de concepts existants
la gestion mémoire manuelle de Zig repose moins sur le rejet de l’OOP que sur une philosophie de Data-Oriented Design (DOD)
conférence associée : présentation DOD d’Andrew
la vraie question était : « que choisir entre des programmeurs lents, un compilateur lent ou une exécution lente ? »
l’équipe Go semble avoir fini par trouver un compromis satisfaisant sur ce point