30 points par GN⁺ 2025-12-06 | 1 commentaires | Partager sur WhatsApp
  • 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 ou ArrayList en 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.Allocator n’est pas implémenté comme une interface
    • À la place, le langage vise une conception orientée données (data-oriented design)
  • 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

 
GN⁺ 2025-12-06
Réactions sur Hacker News
  • Il n’est pas difficile de créer une variable globale mutable en Rust
    il faut simplement utiliser unsafe ou un smart pointer fournissant de la synchronisation
    Rust 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

    • Après plusieurs années avec Rust, je pense que les variables globales mutables sont l’exemple typique de « ce n’est pas parce qu’on peut le faire qu’on doit le faire »
      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é
    • Les formulations du type « c’est trivial, il suffit de ~ » sont le genre de choses que j’ai déjà entendues en C++, Perl ou Haskell
      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
    • Je me demande si le compilateur Rust détecte à la compilation les race conditions entre threads
      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
    • Si je créais un langage, j’interdirais purement et simplement les variables globales mutables
      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::new
    on peut créer une globale mutable comme static FOO: Mutex<T> = Mutex::new(...), et un mutex est nécessaire pour la sécurité mémoire
    le 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

    • Mais comme d’autres développeurs peuvent utiliser un autre 5 à 10 % des concepts, il faut au final en apprendre davantage pour travailler ensemble
      en C, il y a moins de cette complexité
      la complexité reste donc un vrai sujet
    • Dire que « Rust permet aussi l’arena allocation » est exact, mais la plupart des codes Rust/Go suivent comme chemin par défaut des allocations nombreuses et de petite taille
      le sujet n’est pas seulement de savoir si c’est possible, mais la différence de style de programmation par défaut
    • Si l’allocator est un type en Rust, je me demande si, dans un modèle de threads m:n, on peut donner une arena distincte à chaque requête
    • Question également sur le fait de savoir si l’allocator de Rust est global, c’est-à-dire si toutes les allocations sur le tas utilisent le même allocator
    • En mentionnant la vidéo sur le batch allocation de Casey Muratori, quelqu’un note que certains développeurs la comprennent mal et s’en servent pour critiquer à tort le RAII de Rust
      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

    • Mais sur des OS comme Linux qui utilisent l’overcommit, les échecs d’allocation ne se produisent pas réellement
      ce n’est pas quelque chose qu’un langage peut résoudre à lui seul
    • À la question de savoir pourquoi Zig devrait exister alors que Rust existe déjà, je demanderais plutôt « pourquoi Zig alors qu’il y a déjà C ? »
      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
    • Je me demande comment fonctionne l’inférence de taille de pile de Zig en présence de récursion ou d’appels via pointeurs de fonction
    • Zig n’est pas le premier sur ce terrain, et il faudrait regarder l’histoire des langages système depuis JOVIAL en 1958
    • Rust sait aussi bien gérer la pré-allocation
      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 Rust
    append() renvoie une nouvelle slice, qui peut ou non partager la mémoire existante
    il n’y a pas de moyen de réduire la mémoire, et si l’on écrit seulement append(s, ...), on ignore la nouvelle slice
    Go 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

    • En pratique, on peut réduire la mémoire avec slices.Clip
      de plus, écrire simplement append(s, ...) produit une erreur de compilation, donc l’affirmation d’origine est légèrement inexacte
      Go est un langage qui évalue avec prudence l’augmentation de complexité à chaque ajout de fonctionnalité
    • Comme append(s, …) ne compile même pas, je pense qu’un débutant ne peut pas faire cette erreur
    • Il est intéressant de voir que, même avec l’arrivée des génériques en Go, des types comme List[T] ne sont pas largement utilisés
      probablement parce qu’on a rarement besoin de passer directement une liste extensible
    • La plupart des pièges de Go sont clairement documentés dans la spécification et la documentation
      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é

    • Quelqu’un demande une source pour cette affirmation à propos des sanitizers sur Android
  • 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

    • Quelqu’un demande ce que signifie WL
    • Raku est un langage généraliste très expressif, avec dispatch multiple, rôles, typage progressif, évaluation paresseuse et un puissant système de regex intégrés
      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::new ait été pris à tort pour une « allocation implicite »

    • Il est difficile de comprendre comment on peut confondre l’escape analysis de Go avec les allocations explicites sur le tas en Rust
      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

    • L’un des avantages de Go est aussi la cohérence du code, qui facilite l’exploration de grandes bases de code
      retrouver les implémentations d’interfaces est difficile, mais la lisibilité est élevée, ce qui aide le travail en équipe
    • La conférence de Rob Pike « Concurrency is not Parallelism » explique bien la philosophie de concurrence de Go
      il n’y a pas de colored function, la communication par canaux est simple, et l’on peut écrire rapidement du code concurrent correct
    • Mais je pense que la concurrence structurée est un modèle plus simple
      article associé : Structured Concurrency or Go Statement Considered Harmful
    • La nouvelle interface std.Io de Zig ressemble au modèle de concurrence de Go
      le mot-clé go correspond à std.Io.async, les canaux à std.Io.Queue, et select à std.Io.select
    • Un développeur Erlang ne serait probablement pas d’accord avec l’idée que le modèle de concurrence de Go est le plus simple
  • Ce 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

    • Je suis d’accord. Il existe une forte demande de marché pour un langage natif avec GC et un système de types plus puissant
      on a vu OCaml, D, Swift, Nim, Crystal, etc., mais aucun n’a encore dominé le marché
    • J’ai entendu dire que l’OCaml moderne dispose d’un modèle de concurrence très puissant et de performances capables de rivaliser avec Go
    • Le langage Borgo a tenté quelque chose dans cette direction, mais le projet a été abandonné
      à la place, Gleam mérite un coup d’œil
    • La proposition sur les erreurs de Go m’a semblé intéressante
      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
    • L’alternative la plus proche est C#, mais cela reste un langage encore très centré OOP
  • 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

    • Comme dans le texte de Russ Cox de 2009, « The Generic Dilemma »,
      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