1 points par GN⁺ 2 시간 전 | 1 commentaires | Partager sur WhatsApp
  • Il y a environ 3 ans, lorsque l’auteur a implémenté lui-même une VM à bytecode et un garbage collector en Zig et en Rust unsafe, l’ergonomie plus humaine de Zig prenait l’avantage, mais à l’ère des agents de codage, cet avantage est devenu pratiquement insignifiant
  • Le gain de productivité développeur de 1,5 à 5x apporté par les fonctionnalités majeures de Zig est écrasé par le gain de productivité de 100x offert par les agents de codage basés sur Rust
  • L’interface d’allocator de Zig, les entiers à largeur de bits arbitraire, les packed struct, comptime et d’autres fonctions clés brillent surtout quand un humain écrit directement le code
  • Le système de types de Rust est plus efficace pour empêcher à la compilation les erreurs des agents grâce au bounded polymorphism et à l’application d’invariants
  • Dans une situation où le volume de code généré par les agents a été multiplié par 100, les garanties de sûreté mémoire de Rust deviennent un avantage décisif face à Zig

Changement fondamental

  • Zig avait un gros avantage en matière d’ergonomie du code unsafe, mais à mesure que la part de code écrite directement par des humains diminue, la valeur pratique de cet avantage diminue aussi
  • Le gain de productivité de 1,5 à 5x pour les développeurs humains apporté par les fonctionnalités de Zig est éclipsé par le gain de 100x avec des agents de codage en Rust
  • Une grande partie des fonctionnalités emblématiques de Zig améliore surtout la commodité de l’écriture manuelle du code, mais cette différence compte beaucoup moins pour les agents de codage
  • Il y a environ 3 ans, en écrivant une VM à bytecode et un garbage collector en Zig et en Rust unsafe, l’auteur avait eu le sentiment que l’expérience d’écriture de code unsafe était meilleure côté Zig
  • Même en 2026, Zig reste un bon langage, mais Rust est devenu le langage le plus préféré et celui qui s’accorde le mieux avec les agents de codage

L’interface d’allocator de Zig

  • L’interface d’allocator de Zig permet d’appliquer facilement des allocators spécialisés, comme arena ou stack fallback, afin d’optimiser des chemins de code précis
  • Lorsqu’on lit une ligne d’entrée utilisateur, la longueur de l’entrée est théoriquement illimitée et nécessite donc un allocator sur le heap, mais en pratique l’entrée est souvent un mot-clé de recherche ou un chemin, donc bien inférieure à 1 KB
  • std.heap.stackFallback(256, heap_allocator) place un buffer de taille fixe sur la pile, puis ne bascule sur le heap qu’en cas de débordement, ce qui permet de traiter le cas général sans allocation sur le heap
  • Par le passé, Rust ne proposait pas d’équivalent à l’interface Allocator de Zig ; si l’on voulait un Vec<T> avec allocator personnalisé, il fallait copier l’implémentation de la bibliothèque standard puis la modifier
  • Le code source des collections de Bumpalo consistait à forker les collections standard pour les brancher sur un bump allocator
  • Rust nightly a longtemps proposé un trait Allocator, et il semble désormais être arrivé à un niveau suffisamment bon
  • L’Allocator de Rust repose sur des traits et utilise donc le dispatch statique, contrairement aux allocators de Zig qui reposent sur des vtables
  • Rust n’a pas de convention communautaire généralisée consistant à concevoir les structures de données autour de paramètres d’allocator comme en Zig, mais cette limite compte moins maintenant que l’IA peut facilement copier et adapter du code

Entiers à largeur de bits arbitraire et packed struct

  • Les entiers à largeur de bits arbitraire de Zig et les packed struct facilitent des tâches comme l’optimisation du cache CPU dans une approche orientée données, les tagged pointers, le NaN boxing ou les bitflags
  • Lorsqu’on utilise une API Obj-C avec Metal via l’API C du runtime Objective-C, id peut être un tagged pointer et non un pointeur d’objet alloué sur le heap et aligné
  • Si l’on passe un NSNumber taggé à un code qui suppose un alignement, cela peut provoquer de l’UB ; il faut donc pouvoir vérifier à faible coût si l’on a affaire à « un pointeur heap ou un immédiat taggé »
  • Dans une disposition simplifiée des tagged pointers Objective-C, le bit de poids faible indique « pas un pointeur heap », les 3 bits suivants identifient le slot de classe, et les 60 bits restants constituent la payload
  • Zig permet d’exprimer directement la disposition binaire comme type avec enum(u3) et packed struct, par exemple class: TaggedClass, payload: u60
  • En Zig, @bitCast permet d’aller et venir entre un u64 brut et ObjcTaggedPointer, et dans is_ns_number, on peut vérifier is_tagged ainsi que la classe .ns_number
  • Le code équivalent en Rust place des constantes comme TAG_MASK, CLASS_MASK, CLASS_SHIFT et PAYLOAD_SHIFT dans ObjcTaggedPointer(u64), fait des OR à la création et applique des masques à l’accès
  • En Rust, le slot de classe n’est pas un vrai type mais une constante u64, et cette manière d’écrire à la main est moins ergonomique qu’en Zig
  • En Rust, il vaut mieux utiliser des crates comme bitfield ou bitflags, mais elles dépendent toutes deux de proc macros et ne semblent pas aussi satisfaisantes que les packed struct de Zig
  • Avec des agents de codage, le problème du caractère fastidieux de l’écriture manuelle de ce type de code diminue fortement

L’évolution de la valeur de comptime

  • comptime est la fonctionnalité la plus spectaculaire de Zig, et en dehors de quelques langages ésotériques à types dépendants, il existe peu de langages offrant une évaluation à la compilation aussi bonne que celle de Zig
  • En pratique, cette fonctionnalité a fini par moins manquer, et environ 95 % de son usage servait à créer des structures de données génériques paramétrées par type
  • Le motif typique est quelque chose comme fn ArrayList(comptime T: type) type, qui reçoit un type et renvoie un type de struct contenant items: []T, capacity: usize, allocator: Allocator
  • Le système de types de Rust remplace une grande partie des génériques à la Zig basés sur comptime et permet d’imposer davantage d’invariants
  • Dans les 5 % de cas restants, l’absence de comptime est gênante, et il n’existe pas d’alternative fiable en dehors de la génération de code
  • Lorsqu’on veut intégrer en dur dans une structure de données des données de hitbox geometry générées par des outils pendant le développement d’un jeu, en Rust il faut demander à Claude d’écrire un script qui génère un fichier Rust
  • Malgré cela, on n’a pas si souvent besoin d’évaluation à la compilation en pratique

Les avantages du système de types de Rust

  • Le système de types de Rust est jugé comme un échange plus précieux que le comptime de Zig, en particulier parce qu’il est fort dans la zone des traits/typeclasses pour le bounded polymorphism
  • Tenter d’implémenter en Zig un bounded polymorphism du même niveau est extrêmement difficile
  • Le système de types de Rust permet d’imposer davantage d’invariants, ce qui aide à empêcher les erreurs fréquentes des agents de codage
  • Dans du code de jeu, on utilise la crate euclid pour éviter la confusion entre espaces de coordonnées, un problème fréquent en programmation graphique
  • En créant des types spécialisés pour chaque espace de coordonnées, comme Point<Screen> ou Point<World>, on empêche à la compilation de mélanger par erreur les coordonnées monde et écran
  • Si l’on définit WorldPoint, WorldVector et ScreenPoint comme types distincts, l’addition d’un point et d’un vecteur du même espace reste autorisée
  • Translation2D::<f32, WorldSpace, ScreenSpace> permet de convertir explicitement de l’espace monde à l’espace écran
  • À l’inverse, un code comme let bad: ScreenPoint = player;, qui assigne directement un WorldPoint à un ScreenPoint, n’est pas autorisé

L’effet de devoir moins traiter les problèmes mémoire

  • Si les agents de codage permettent d’écrire 100 fois plus de code, alors la quantité de problèmes mémoire à examiner dans du code Zig augmente elle aussi d’un facteur 100
  • Sans vérification formelle, la surface de l’espace de recherche à explorer pour trouver des bugs devient beaucoup plus grande
  • Dans la situation actuelle, où le volume de code généré a fortement augmenté, Rust devient plus attractif
  • Le compromis traditionnel de Rust était qu’il nuisait à la productivité des développeurs peu familiers avec le borrow checker, mais avec des agents de codage, l’importance de cet inconvénient diminue fortement
  • Même lorsqu’on utilise unsafe en Rust, on peut faire exécuter à un agent de codage des outils comme miri pour vérifier qu’il n’y a pas d’UB et qu’on ne viole pas les règles d’aliasing de Rust

Conclusion

  • Zig reste un langage qui manque à l’auteur et demeure un bon langage
  • Dans la manière de travailler de 2026, Rust est davantage préféré, et son adéquation avec les agents de codage est également meilleure

1 commentaires

 
GN⁺ 2 시간 전
Avis sur Lobste.rs
  • Mon ancien lead d’équipe avait une conviction assez forte : le copier-coller de code n’est pas toujours une mauvaise chose
    À cause du principe DRY, ça paraissait instinctivement faux ou au moins polémique, mais c’était quelqu’un de très pragmatique, et il appliquait surtout cette idée à de grosses bases de code de test
    Son raisonnement était qu’au lieu de forcer une interface partagée trop astucieuse, une base de code simple, mais plus grosse et plus redondante, peut être plus facile à maintenir
    Avec les LLM ces temps-ci, je reviens à la même idée, et je l’applique maintenant à des parties plus importantes du logiciel
    La génération de code est rapide, et les LLM semblent aussi avoir plus de chances de bien s’en sortir sur une base de code simple mais redondante

    • Surtout pour les tests, je suis complètement du côté WET, c’est-à-dire « write everything twice »
      Si on met trop d’abstractions dans les tests pour réduire la duplication, ils deviennent plus difficiles à comprendre, avec en plus le risque d’être subtilement faux
      Pire encore, si on réutilise les abstractions du code testé, les tests peuvent se tromper de la même manière que ce code
      Et contrairement au code applicatif, les tests sont en pratique « composés » gratuitement
      Tant qu’on n’a pas gravement saboté le harnais de test, on peut ajouter ou supprimer des tests à volonté sans affecter les autres, et comme il n’y a pas de friction d’intégration, cela fait une raison de moins d’éviter la duplication
    • D’accord
      Dans les tests, j’ai déjà vu ça formulé comme DAMP : « Descriptive and Meaningful Phrases », un principe qui met l’accent sur la lisibilité plutôt que sur l’unicité
      Ce principe peut créer de la duplication en répétant du code similaire, mais il rend aussi les tests plus manifestement corrects
      https://testing.googleblog.com/2019/12/…
      Il existe une idée similaire dans la communauté Go : « un petit copier-coller vaut mieux qu’une petite dépendance » https://go-proverbs.github.io/
    • La première fois que j’ai lu Repeat yourself, do more than one thing, and rewrite everything, ça m’a vraiment parlé
    • La partie disant que cela « sonnait instinctivement faux » n’a en réalité jamais été fausse au départ
    • En regardant Andrew Kelley coder, j’en ai tiré une bonne leçon
      Je lui suis reconnaissant de partager ses sessions de live coding
      Si je me souviens bien, quand il commençait quelque chose, il allait souvent chercher le code le plus proche de ce qu’il voulait construire, le copiait en entier, puis le modifiait à partir de là
      Je me disais : « il ne va pas s’asseoir un moment pour réfléchir à l’abstraction commune entre les deux ? », mais il avançait simplement au copier-coller et était bien plus productif que moi
  • C’est intéressant quand on pense au timing de la réécriture IA de Bun de Zig vers Rust https://xcancel.com/jarredsumner/status/2053063524826620129#m

    • Parmi les 150 PR récemment fusionnées dans Bun, 108 concernaient la sûreté mémoire : oublis de nettoyage sur les chemins d’erreur, use-after-free, lectures non initialisées, accès hors limites, réentrance, etc.
      Parmi elles, 75 n’auraient pas compilé dans un langage avec destructeurs, sémantique de déplacement et borrow checker
      En gros, une PR déployée sur trois revenait à « on a oublié de libérer quelque chose sur un chemin d’erreur »
      Sur ces 108, environ 88 se trouvent côté Zig, et les quelque 14 côté C++ relèvent surtout de catégories résiduelles qui subsistent dans n’importe quel langage, comme les cycles de références et les races de concurrence dans le GC
      Donc l’écart Zig→Rust est réel, et les bugs Zig sont précisément du type que destructeurs et ownership corrigent, tandis que le côté C++ est déjà proche du plancher
      Sans garanties plus fortes à la compilation, ça restera un jeu du chat et de la souris
      L’idée est de ne pas continuer à corriger individuellement la plus grosse catégorie de bugs, mais de l’éliminer structurellement
      bun/docs/rust-rewrite-plan.md at claude/phase-a-port · oven-sh/bun · GitHub
  • Le passage disant que « dans les 5 % restants, l’absence de comptime est pénible, et que la seule façon d’arriver de manière fiable à un résultat équivalent est la génération de code » n’est pas très clair sur ce que l’auteur veut dire
    Parce qu’il ne dit rien des macros procédurales

    • Le comptime de Zig est vraiment génial, mais le système de macros de Rust n’est certainement pas à négliger
      C’est un peu pénible à bien concevoir, mais on peut faire énormément de choses avec
      J’ai aussi l’impression que la génération de code a une mauvaise réputation un peu imméritée
      J’ai résolu pas mal de problèmes pénibles via génération de code avec des scripts build.rs, et ça fonctionne bien
      Bien sûr, je le regretterai peut-être plus tard
  • La thèse centrale du texte me semble être à peu près la suivante

    1. Dans un cas particulier où l’on personnalisait les allocateurs par structure de données, le coût du copier-coller de code était un problème difficile
    2. Certaines fonctionnalités du système de types de Rust sont plus pratiques. Cela dit, dans cet exemple précis, j’ai du mal à croire qu’on ne puisse pas demander à un agent de porter en Zig la conception de types Rust et d’imposer la forme de l’API avec comptime
    3. Pour des fonctionnalités comme les bitflags ou le SoA, la lisibilité du code n’a pas beaucoup d’importance
    4. Si la compilation garantit l’absence d’erreurs de sûreté mémoire, on peut relire « 100 fois » plus de code
      Rust est certes un bon langage, mais là ça me paraît quand même excessif
  • On dirait une pub pour des agents de code

    • On dirait plutôt une réflexion honnête sur la façon dont les agents de code influencent le choix d’un langage de programmation
    • Ou alors c’est peut-être une pub en psychologie inversée pour Zig…
  • Cela vient d’un billet lié du même auteur : en programmation graphique, une erreur très fréquente est de confondre les espaces de coordonnées, et le système de types est assez puissant pour exprimer quels espaces et quelles transformations sont valides
    On peut dire la même chose des devises, des distances et poids en unités SI versus unités impériales, des chaînes validées versus chaînes fournies par l’utilisateur, et des secrets
    De même, si on les gère bien, les types d’état peuvent empêcher des états impossibles ou non sound
    Mais personnellement, la fonctionnalité que je veux le plus en Rust, c’est l’élimination complète des data races
    Les langages managés ont eux aussi des data races
    Le « utilise simplement Go » : dans Go, tout est mutable par référence entre threads, avec en prime la gymnastique des slices
    Même JavaScript, qui se trouvait autrefois du côté le plus pur et le plus sûr et passait pour un langage complètement jouet, fait de chaque await une course potentielle
    Sans même parler du caractère maléfique du modèle everything-is-an-EventEmitter
    Donc oui. S’il n’y avait que le GC… 🤫

  • J’ai l’impression que ça cache un peu l’essentiel
    Les agents de code sont aussi très bons en Python et JavaScript
    Dire qu’ils sont meilleurs qu’en Rust relève un peu du geste vague subjectif, mais ça ne veut pas dire que je choisirais ces langages pour beaucoup de travaux
    Je me demande si le problème vient du fait que les fonctionnalités de Zig changent souvent, ou si c’est simplement parce que c’est un langage plus récent et que les données d’entraînement de l’IA y sont plus brouillonnes

  • J’ai l’impression que Zig est plus difficile à écrire que Rust, mais plus facile à lire
    À l’ère de l’IA, on lit plus de code qu’on n’en écrit, donc j’ai tendance à préférer Zig

  • Vu le volume de code généré aujourd’hui, dire que Rust est plus attractif n’est qu’une première étape
    Plus les ordinateurs écriront de code, plus les langages formels seront avantagés
    Cela ressemble à une nouvelle étape dans le débat sur le typage dynamique
    Du genre : « le typage dynamique est plus simple pour les humains, alors pourquoi faut-il préciser la même chose trois fois pour les machines ? »
    Les types, les durées de vie… quoi d’autre pourrait être plus facile à écrire et à consommer pour les machines ?
    Je me demande jusqu’à quel point il deviendra difficile pour les humains de coder directement dans les langages que les ordinateurs utiliseront pour écrire du code