- En gestion mémoire, Zig propose une approche plus simple et plus intuitive que Rust
- Le borrow checker de Rust est puissant, mais pour le développement de petits outils CLI, il entraîne une complexité excessive et une charge pour le développeur
- La gestion mémoire manuelle de Zig permet d’obtenir efficacement une bonne sûreté mémoire avec les bons outils et un peu de discipline de la part du développeur
- La sûreté d’un programme ne se limite pas à la sûreté mémoire : comportement prévisible, performances maîtrisées, protection des données et d’autres éléments sont tout aussi importants
- Rust convient aux systèmes de grande envergure, mais pour de petits outils CLI pratiques, Zig est plus avantageux en productivité de développement et en maintenance
Vue d’ensemble
Ces derniers temps, lorsque je crée des outils CLI, j’ai tendance à choisir Zig en priorité plutôt que Rust.
Les bases de la gestion mémoire : pile et tas
- La pile est une zone mémoire rapide à taille fixe qui stocke des données très temporaires comme les paramètres de fonction, les variables locales et les adresses de retour
- Le tas est la zone dédiée à l’allocation dynamique de mémoire, utilisée lorsque la durée de vie des données est longue ou que leur taille est déterminée à l’exécution
- La pile est structurellement simple mais l’espace y est limité, tandis que le tas demande plus d’attention en matière de vitesse et de fragmentation
Le Borrow Checker de Rust
- Le borrow checker de Rust garantit la sûreté mémoire à la compilation
- Il impose des règles sur les références, la propriété et les durées de vie (
lifetime), ce qui permet d’éviter à l’avance des erreurs comme le déréférencement de pointeurs nuls ou les pointeurs pendants - En revanche, la sûreté mémoire n’est vérifiée qu’au moment de la compilation, et cela ne supprime ni les erreurs de l’utilisateur ni les problèmes inhérents à une conception complexe de la propriété
Cas pratique : mon propre CLI Notes
- En essayant d’écrire en Rust un CLI pour gérer mes notes personnelles, j’ai dû repenser péniblement la structure à cause du borrow checker
- À l’inverse, avec Zig, le simple usage d’allocateurs permet de créer des index basés sur des pointeurs et d’effectuer librement modifications et suppressions de façon bien plus simple
- Le borrow checker de Rust a un objectif clair, mais Zig permet, avec des bases solides en gestion mémoire et un peu de discipline, d’atteindre un haut niveau d’efficacité et de sûreté
Quand la sûreté d’un outil CLI ne se résume pas à la sûreté mémoire
- La véritable sûreté d’un produit inclut de nombreux éléments : comportement prévisible, retours utiles en cas d’erreur, protection des données sensibles, résistance aux attaques, etc.
- Ni Rust ni Zig ne peuvent être qualifiés de « sûrs » s’ils ne remplissent pas aussi ces conditions au-delà de la seule sûreté mémoire
- Par exemple, si un CLI écrase silencieusement des données en cas d’erreur ou configure mal les permissions de fichiers, l’utilisateur peut rencontrer de graves problèmes
-
La sûreté des outils CLI
- Comportement prévisible : il faut garantir un comportement cohérent et clair, même avec une entrée invalide ou dans des situations imprévues
- Prévention des crashs et de la corruption des données : il faut gérer les erreurs avec élégance et éviter la corruption des données ou les crashs non signalés
- Maîtrise des performances : même en traitant de gros volumes de données, il ne doit pas y avoir de surconsommation de ressources ni de baisse de réactivité
- Protection des informations sensibles : il faut prêter attention aux fichiers temporaires et aux réglages de permissions
- Résistance aux attaques : il faut être robuste face à la validation des entrées, aux débordements mémoire, aux attaques par injection, etc.
Forces et limites du Borrow Checker de Rust
-
Forces
- Blocage des data races et des références concurrentes invalides : le compilateur garantit les règles d’une unique référence mutable et de multiples références immutables
- Fortes garanties à la compilation : la plupart des bugs liés à la mémoire sont bloqués avant l’exécution
- Détection précoce des bugs : c’est un grand avantage dans les services en production ou les systèmes concurrents
-
Limites et inconforts
- Surcharge cognitive : même pour de petites tâches CLI, il faut forcément réfléchir à la propriété, aux durées de vie et à la gestion des références
- Boilerplate / distorsion structurelle : wrappers comme
RcetRefCell, abus declone, refonte de structure, etc. font passer l’attention de la « résolution du problème » à la « satisfaction du compilateur » - Impuissance face aux bugs logiques ou d’état : seules les règles mémoire sont garanties ; la prévisibilité, les erreurs de logique ou l’intégrité des données ne le sont pas
- Complexité des cas limites : des conflits de durées de vie surviennent facilement avec les caches, l’état global ou les index mutables
- En conséquence, dans de petits projets CLI, le borrow checker de Rust devient une « taxe mentale » pour le développeur et peut rendre le tout plus complexe que nécessaire dans la pratique
L’approche de Zig en matière de sûreté et de simplicité
- Zig repose sur des vérifications de sûreté optionnelles et une gestion mémoire manuelle
- Le concept d’allocateur est intégré, ce qui permet de mettre en place une utilisation de la mémoire structurée et prévisible
- Il est aussi possible de créer des allocateurs personnalisés pour définir une stratégie de gestion mémoire adaptée aux caractéristiques du projet
- Grâce à l’instruction
deferde Zig, la libération automatique en fin de portée et le nettoyage des ressources sont bien plus intuitifs - Contrairement à Rust, la responsabilité du développeur est mise en avant, ce qui demande de la discipline, mais avec une bonne conception, atteindre et maintenir la sûreté mémoire reste facile
- Le code Zig est concis, et les modifications structurelles autour des pointeurs, listes et index sont bien plus simples qu’en Rust
- Il est possible d’implémenter un code aussi sûr et efficace sans être autant contraint qu’en Rust
- En plus, la fonctionnalité comptime de Zig aide énormément pour l’exécution de code à la compilation, les tests et les optimisations
L’importance de l’expérience développeur (Developer Ergonomics)
- L’expérience développeur (ergonomics) englobe des éléments allant de la syntaxe du langage aux outils, à la documentation et à la communauté
- Rust garantit au final la sûreté mémoire grâce à des règles très strictes, mais l’excès de règles et de cérémonial fait baisser la productivité
- Zig met davantage l’accent sur une conception pilotée par le développeur, ce qui rend le code plus facile et plus rapide à écrire, modifier et comprendre
- Avec son code intuitif, ses itérations rapides et sa faible charge mentale, Zig aide le développeur à se concentrer sur la résolution du problème plutôt que sur l’outil
- Zig fait confiance au développeur et lui donne les bons outils ainsi que des choix, tandis que Rust peut sembler trop directif et restrictif
- Au lieu de simplement « protéger le développeur de ses erreurs », un environnement favorable aux développeurs doit aussi lui garantir la possibilité d’apprendre et de progresser à travers ses propres erreurs
Conclusion
- Dans les domaines où les forces de Rust brillent vraiment, comme les grands systèmes multithread et de longue durée d’exécution, Rust reste le meilleur choix
- En revanche, pour de petits outils CLI concrets, la légèreté, la simplicité et la rapidité d’implémentation et de maintenance de Zig sont plus adaptées
- La sûreté mémoire n’est qu’une pièce du puzzle de la sûreté ; des éléments essentiels aux outils CLI comme le comportement prévisible, la maintenabilité et la robustesse sont plus faciles à atteindre avec Zig
- En fin de compte, ce qui compte n’est pas le « meilleur langage », mais le choix de l’outil adapté à mon workflow et aux caractéristiques du projet
- Zig est un langage parfaitement adapté au développement de petits outils, en combinant « sûreté mémoire + faible coût cognitif + convivialité pour le développeur / productivité »
3 commentaires
On dirait que l’écosystème n’est pas encore aussi stabilisé que celui de Rust.
Comme Zig introduit très souvent des breaking changes dans ses nouvelles versions... même pour de petits projets, il vaut mieux mettre en place une CI et assurer un suivi continu.
Avis Hacker News
L’avantage de Zig, c’est qu’il permet de continuer à penser comme un développeur C, mais à un certain niveau, c’est aussi simplement une question d’habitude
Les développeurs suffisamment familiers avec Rust ne se battent plus avec le borrow checker, ils finissent par penser directement dans une structure de code compatible
Dans Rust, une approche du type « object soup » fonctionne mal, mais je ne pense pas que ce soit fondamentalement une méthode plus simple, c’est juste qu’elle paraît plus facile parce qu’on y est habitué
Si on admet que l’ergonomie est difficile à mesurer ou à quantifier, ce genre de débat reste forcément flou
L’idée de « se battre avec le borrow checker » vient d’une époque où l’on ne comprenait que les lifetimes lexicaux dans Rust
D’après mon expérience, les développeurs Rust expérimentés mettent des
Arcun peu partout et s’en servent presque comme d’un garbage collection automatiqueJ’ai aussi vu beaucoup de projets open source en Rust, écrits par des développeurs chevronnés, qui utilisent
Arc,Clone,Copy, etc. un peu partoutL’avantage de Zig, c’est qu’on peut développer de manière familière comme en C tout en bénéficiant de mécanismes de sûreté fournis par le langage et les outils
nilJe ne suis globalement pas d’accord avec le billet d’origine
Rust, comme C ou Zig, oblige à réfléchir aux lifetimes, à l’ownership et à la portée des emprunts ; la différence, c’est l’aide du compilateur
L’être humain fait des erreurs, même s’il est intelligent, quand il est fatigué ou distrait ; reconnaître cela, c’est précisément faire preuve de sagesse
L’ensemble des programmes que le compilateur Rust considère sûrs n’est pas assez large, donc il rejette assez souvent des programmes pourtant valides
Exemple : si dans une struct
Foo,baretbazsont chacun des chaînes, alors prendre une référence mutable surbarpuis une référence immuable surbazne compile pas, et on est obligé de contourner artificiellement le problème en restructurant le codePour répondre à cela, devoir modifier son code vers une conception de second ou troisième choix juste pour éviter des cas « en réalité corrects mais refusés à la compilation » est déjà une charge importante
L’exemple ci-dessus me semble vraiment excellent ; j’aimerais demander si je peux en parler dans mon blog ou dans un article
En voyant ce code, je me sens au contraire encore moins sûr de l’argument
Il faut se rappeler que tous les programmes n’ont pas forcément besoin d’être aussi « sûrs »
Nous avons grandi en profitant de beaucoup de logiciels Unsafe : Star Fox 64, MS Paint, FruityLoops, etc.
J’ai lu qu’Andrew Kelley, le créateur de Zig, avait créé Zig parce qu’il n’existait pas d’environnement adapté au développement de logiciels de production musicale (DAW), et je pense que Zig convient bien à ce type de logiciel créatif
Si quelqu’un est très sensible aux bugs mémoire, il peut simplement utiliser Rust
Je crois même que Super Mario World était plus amusant à cause de ses bugs mémoire
« Sûr » est une manière abrégée de dire « mon programme fonctionne comme prévu »
unsafe)Je suis un peu perdu : est-ce que le fait qu’on trouve mon avis mauvais signifie qu’on pense que la sûreté mémoire n’a pas d’importance ?
J’ai trouvé dommage que la valeur du borrow checker soit sous-estimée
Le borrow checker de Rust garantit à la compilation l’absence d’accès mémoire invalides
Bien sûr, cela a pour contrepartie l’inconfort de devoir réorganiser son code pour respecter les règles du compilateur
Quand j’utilise Rust de mon côté, je n’ai jamais eu l’impression que les annotations de lifetime étaient « fausses » ; c’était un peu fastidieux, mais on s’y habitue vite
Tant qu’on n’utilise pas
unsafe, deux threads ne peuvent pas écrire simultanément dans la même mémoire en RustJe ne comprends pas vraiment l’argument « pourquoi Zig semble plus pratique pour les outils CLI » ; Rust garde un avantage clair pour éviter les CVE (vulnérabilités)
En pratique, je fais déjà la plupart de mes tâches dans des langages à GC, et quand je contribue dans d’autres langages, Rust, Zig ou C/C++ me conviennent tous
Un outil CLI n’aurait rien de spécial ?
Dire que sans
unsafe, deux threads ne peuvent pas écrire en même temps dans la même mémoire n’est pas aussi simple que çaunsafe, même lorsqu’un problème peut effectivement apparaîtreJe suis d’accord sur le fait que les backlinks sont trop compliqués à implémenter en Rust
C’est possible avec
Rc,Weak,RefCell,.borrow(), etc., mais ce n’est pas simplePour un programme de courte durée, l’allocation en arena est aussi une option, ce qui semble correspondre à ce qu’on entend par outil CLI
Rust donne vraiment le meilleur de lui-même dans les applications énormes, multithreadées et de longue durée
J’ai réellement écrit un gros client de métavers en Rust, avec des dizaines de threads tournant 24 h/24 sans fuites mémoire ni crash
Faire la même chose en C++ demanderait une équipe QA et des outils comme Valgrind, tandis qu’un langage de script serait beaucoup trop lent
J’ai aussi créé en Rust un avion de simulation physique qui tient compte jusqu’à la courbure de la Terre et aux variations de gravité
Zig est séduisant, mais D existe toujours, et personnellement j’ai l’impression que D est davantage le remplaçant de C/C++ que je recherche
La syntaxe de Zig me paraît un peu étrange, et Rust est déjà devenu central dans l’écosystème
Go aussi occupe une place importante dans de nombreux outils, et dans le domaine de l’IA, c’est probablement le langage le plus utilisé après Python
Avant Rust, il y avait déjà le débat Go vs. D, et j’avais même acheté un manuel sur D avant de finalement passer à Go
int64me semblaient plus intuitifsD est bien, mais il n’a pas eu l’application phare qui lui permettrait de se démocratiser
Je ne vois pas très bien pourquoi il faudrait absolument utiliser Rust ou Zig pour faire des outils CLI
Le goulet d’étranglement, c’est l’I/O, pas le fait que le GC soit lent
Les questions de GC ne sont pas vraiment pertinentes hors des domaines gourmands en mémoire comme les jeux ou les bases de données
Je veux surtout souligner que, plus que le débat sur la sûreté mémoire, je réfléchis davantage aux raisons de choisir un langage sans GC
Si c’est simplement parce que « le no-GC, c’est amusant », alors cela suffit en soi et il n’y a pas besoin d’en débattre
Un temps de démarrage immédiat, sans latence à l’exécution, est très utile
Construire des CLI en Go m’a semblé très satisfaisant, même si je n’aime pas particulièrement le langage Go lui-même
Je choisis en priorité des langages qui ont des sum types, du pattern matching et du support
asyncÀ propos de l’idée selon laquelle le développement sans GC ne concernerait que le jeu vidéo
Le débat sur le GC a aussi un petit côté effet de mode
J’ai créé un petit outil de notes avec les mécanismes intégrés de borrow/référencement de Rust, et ce n’était pas aussi complexe que je l’imaginais
Si l’on imagine une structure qui stocke l’index des notes dans une liste et les relie via une map, la différence de performance est quasi nulle et on ne perd rien en sûreté
Même en cas d’erreur d’index, on obtient au moins une erreur de dépassement de bornes, ce qui est bien préférable à l’écrasement de la mémoire du noyau
Même pour le
printf debugging, c’est beaucoup plus simple et intuitifLes raw pointers ou les références ne devraient en général être utilisés que là où c’est vraiment nécessaire, comme un allocator ou un runtime
async; pour la logique métier courante, une approche basée sur des index convient mieuxC’est d’ailleurs pour cela que l’async de Rust a notoirement des problèmes avec les self-referential structs et tout ce qui touche à
PinLes pointeurs vers des valeurs stockées dans un
vecdeviennent invalides en cas derealloc, etc., et dans ce cas Miri signale immédiatement une erreurEn tant que développeur C++, si je cherchais un langage sûr, j’aurais tendance à penser que Swift est le plus adapté
On s’adapte plus vite à un langage qui nous est familier ou qui lui ressemble
Swift a récemment renforcé son support cross-platform, et plusieurs personnes actuellement impliquées dans le comité de standardisation C++ y participent
Mais ses liens avec Apple et l’absence de framework UI natif limitent encore un peu son expansion hors de l’écosystème Apple
J’aimerais que Swift gagne en popularité
Si quelqu’un a des ressources pour comparer Swift à Zig/C, je suis preneur
On dit que Zig permet aussi de développer des logiciels sûrs du point de vue mémoire avec un minimum de prudence, mais en réalité on peut dire la même chose de C si on l’utilise avec assez de discipline
Au final, ce « petit effort de discipline » manque souvent dans le monde réel, et c’est bien là que les problèmes apparaissent
Zig résout en plus les problèmes suivants
comptimeet des temps de compilation des dizaines de fois plus rapides que C++/Rust sont aussi de vrais atoutsSi C échoue depuis plus de 50 ans sur cette question de discipline, c’est que c’est plus difficile que « la voie de Shaolin »