Pourquoi le comptime de Zig est salué
(scottredig.com)- La métaprogrammation à la compilation (comptime) de Zig se lit comme du code runtime, tout en permettant de produire du code d’exécution aussi efficace qu’une fonction C écrite à la main pour des tâches répétitives comme sommer les champs d’une structure
- Au lieu d’une syntaxe dédiée aux génériques, l’usage de
comptime T: typeet deanytypepour gérer la programmation générique est l’un des principaux cas d’utilisation du comptime de Zig - Le runtime, le comptime et le système de build partagent le même langage Zig, ce qui permet même d’exécuter une fonction comme Fizz Buzz pendant la compilation pour produire un tableau précalculé
- Le compilateur évalue d’abord les parties possibles, et ne conserve comme bytecode de sortie destiné à devenir du code machine que les parties nécessitant des valeurs runtime
- Le comptime de Zig peut résoudre des problèmes similaires à la génération de code textuel, mais sans remplacer arbitrairement des mots-clés comme les macros C, ce qui réduit la charge de lecture et de débogage
Comment lire le comptime de Zig
- Le comptime de Zig est une fonctionnalité de métaprogrammation, mais lorsqu’on lit le comportement final, il n’est pas nécessaire de se préoccuper fortement de la distinction entre compilation et runtime
- Le troisième point de
zig zen, « Favor reading code over writing code », sert de critère important- La métaprogrammation fondée sur les macros ou la génération de code peut créer deux couches : le code source et le code étendu
- Cette couche d’indirection complique la lecture du code, le débogage et la modification du comportement
- Le code runtime qui calcule la somme du tableau
[3]i64 = .{1,2,3}est comparé côte à côte avec du code comptime qui additionne les champsa,b,cde la structureMyStructinline for (comptime std.meta.fieldNames(MyStruct))parcourt les noms de champs@field(my_struct, field_name)accède à un champ à partir d’un nom de champ connu à la compilation
- L’exemple de somme des champs d’une structure ressemble à de la réflexion runtime, mais l’exécutable ne conserve qu’un code aussi efficace qu’une fonction de somme écrite à la main pour ce type de structure
- Le processus de transformation de l’exemple ne reproduit pas exactement l’implémentation de Zig : il s’agit plutôt d’une explication conceptuelle
- L’équipe cœur de Zig travaille sur un débogueur capable d’exécuter pas à pas du code mêlant comptime et runtime comme dans l’exemple
Traiter les génériques avec comptime
- Zig ne possède pas de fonctionnalité générique distincte
- Pour rendre un type générique, on écrit une fonction qui reçoit un type et renvoie un type
- Exemple :
pub fn GenericMyStruct(comptime T: type) type - Les champs
a,b,cde lastructrenvoyée utilisent tous le typeT
- Exemple :
- Les fonctions génériques peuvent être écrites de la même manière
- Exemple :
fn quadratic(comptime T: type, a: T, b: T, c: T, x: T) T - On peut préciser explicitement l’argument de type, comme dans
quadratic(f32, ...)etquadratic(i64, ...)
- Exemple :
- Si le type d’un argument n’est pas important pour d’autres parties de la signature de fonction, il peut être inféré avec le type spécial
anytype - Cette approche ne suffit pas à expliquer tout comptime, mais elle constitue un point de départ pour comprendre les opérations génériques courantes en Zig
Exécuter le même code Zig à la compilation
- Zig utilise le même langage pour le runtime, le comptime et le système de build
- L’exemple Fizz Buzz commence par écrire la fonction
fizzBuzz(writer)comme une fonction d’affichage runtime ordinaire- Elle parcourt les nombres de
1à100 - Elle affiche
fizzbuzzpour les multiples de3et de5,fizzpour les multiples de3,buzzpour les multiples de5, et le nombre dans les autres cas
- Elle parcourt les nombres de
- Si l’on exécute cette même fonction
fizzBuzzdans un bloccomptime, toute la sortie peut être précalculéestd.io.countingWriter(std.io.null_writer)calcule d’abord le nombre d’octets nécessaires- Un tableau
buffer: [cw.bytes_written]u8est créé avec la longueur calculée - On écrit de nouveau dans
std.io.fixedBufferStream(&buffer)pour créer la valeurfull_fizzbuzz
- Si l’on ne mesure que la section centrale, la version précalculée s’exécute environ 9 fois plus vite
- L’exemple lui-même étant petit, le temps d’exécution total dépend davantage d’autres facteurs
- Le comptime et le runtime diffèrent par les valeurs et fonctionnalités accessibles
- Les valeurs
comptime_int,comptime_floatettypene sont accessibles qu’en comptime - Certaines fonctions n’acceptent que des arguments comptime et deviennent de fait réservées au comptime
- Les appels système et les fonctionnalités qui les utilisent ne sont accessibles qu’au runtime
- Les valeurs
- Le code qui n’utilise pas ces fonctionnalités réservées se comporte de la même manière en comptime et au runtime
Comprendre comptime comme une évaluation partielle
- On peut voir comptime comme une évaluation partielle (partial evaluation) qui a lieu pendant la compilation
- L’évaluation partielle consiste, lorsqu’une fonction ne reçoit qu’une partie de ses arguments, à remplacer et calculer d’abord les expressions qui n’utilisent que des valeurs déjà connues, puis à créer une nouvelle fonction qui ne prend que les arguments inconnus restants
- L’exemple de somme d’un tableau montre le processus d’évaluation étape par étape
- La boucle
forest décomposée en instructions individuelles pour chaque itération array[0],array[1]etarray[2]sont respectivement remplacés par1,2et3sumest mis à jour de0 → 1 → 3 → 6- À la fin, le code est simplifié jusqu’à une forme comme
std.debug.print(..., .{6})
- La boucle
- L’exemple de somme des champs d’une structure se lit de la même manière
- Comme
inline forest exécuté en comptime, il est déplié en instructions individuelles pour les champs"a","b","c" @field(my_struct, "a")devientmy_struct.a- La fonction finale contient directement
sum += my_struct.a,sum += my_struct.b,sum += my_struct.c
- Comme
- Cette approche permet d’exprimer directement l’intention dans le code, tout en réduisant la nécessité de laisser des commentaires demandant de mettre à jour manuellement la fonction de somme lorsque les champs changent
Évaluation à la compilation et émission de code runtime
- Le comptime de Zig peut aussi être vu comme une combinaison d’évaluation à la compilation et d’émission de code runtime
- Le code d’entrée est exécuté par le compilateur
- Les instructions connues à la compilation sont évaluées immédiatement
- Les instructions qui nécessitent des valeurs runtime sont ajoutées au code de sortie
- Cette perspective est la plus proche du fonctionnement réel du compilateur Zig
- Zig commence par analyser la syntaxe
- Il transforme le code en bytecode pour une machine virtuelle
- La VM évalue les parties possibles et émet un nouveau bytecode pour les parties qui doivent être traitées au runtime
- Ce nouveau bytecode est ensuite converti en code machine
- Une condition comme
ifavec une entrée runtime émet les deux chemins - Le code mort ne fait pas l’objet d’une analyse sémantique
- Même si l’on écrit une fonction incorrecte, elle peut ne provoquer aucune erreur de compilation tant qu’elle n’est pas effectivement utilisée
- Cette propriété rend la compilation plus efficace et permet une compilation conditionnelle naturelle sans
#ifdef
- Les noms de types en Zig sont eux aussi des expressions qui évaluent des valeurs
typeen comptime- Même un nom de type qui semble simple, comme le type d’un argument de fonction, est le résultat d’une évaluation comptime
- C’est cette propriété qui rend possibles les exemples de génériques précédents
- Si nécessaire, on peut calculer un type avec des expressions plus complexes
- L’analyse statique de Zig est plus complexe que celle de nombreux autres langages à typage statique
- Pour déterminer tous les types, une grande partie du compilateur doit être exécutée
- Tant que les outils n’ont pas rattrapé leur retard, les fonctionnalités d’éditeur comme l’autocomplétion ne fonctionnent pas toujours parfaitement
Rapport avec la génération de code textuel
- Le comptime de Zig diffère dans sa forme de la génération de code textuel, mais il peut résoudre des problèmes similaires
- La fonction de génération de code
writeSumFnaffiche le code source de la fonctionsumFieldsavecwriter.print- Le code exécuté dans le générateur correspond à la partie comptime de Zig
- Le code produit par le générateur correspond au code runtime
- La version comptime de Zig exprime la même logique de manière plus directe
std.meta.fieldNames(MyStruct)récupère les noms des champsinline foret@fieldconstruisent le code d’accès aux champs
- La génération de code qui utilise des informations de type devient plus simple en Zig
- L’approche par génération textuelle doit résoudre séparément la question de l’origine des informations sur les noms de types et de champs
- Zig permet de manipuler directement le type lui-même et les informations de champs en comptime
- Lorsqu’on utilise des entrées comme une spécification externe, on peut inclure le contenu du fichier avec
@embedFile, puis le parser normalement - Dans l’exemple de structure générique, comptime traite les références aux noms de types plus directement que la génération de code textuel
- La génération textuelle doit concaténer des chaînes comme
MyStruct_{s}pour maintenir des noms cohérents - Comptime utilise directement une fonction comme
GenericMyStruct(T)
- La génération textuelle doit concaténer des chaînes comme
- Il existe aussi des exceptions
- Il est possible de créer un type dont les noms de champs sont déterminés en comptime
- Dans ce cas, il faut appeler une fonction intégrée avec une spécification contenant la liste des définitions de champs
- On ne peut pas définir de déclarations comme des méthodes sur un tel type
- Cela ne limite pas l’expressivité du code lui-même, mais limite la forme de l’API qui peut être exposée à d’autres codes
- La plupart des opérations raisonnables possibles avec des macros textuelles comme en C sont aussi possibles avec comptime, mais la forme du code peut être très différente
- Zig ne fournit pas de mécanisme permettant de remplacer des mots-clés par d’autres noms comme le font les macros textuelles
Pour aller plus loin et exemples dans la bibliothèque standard
- Zig lui-même n’est pas un langage qui repose uniquement sur comptime, et l’on peut en apprendre davantage sur le site officiel
- La référence du langage couvre les fonctionnalités concrètes de comptime
- La bibliothèque standard de Zig contient des exemples d’utilisation de comptime
- La fonction de formatage utilisée par
std.debug.printest une puissante fonction générique- Beaucoup de langages parsèrent la chaîne de formatage au runtime et peuvent ajouter un validateur distinct pour détecter les erreurs tôt
- Zig parse la chaîne de formatage en comptime, produit un code de sortie efficace et effectue aussi toutes les validations à la compilation
ArrayListest un conteneur générique relativement simple mais complet- La fonction
mainde Zig peut avoir plusieurs types de retour, et ce n’est pas de la magie du compilateur : c’est géré par du code comptime ordinaire
- La fonction de formatage utilisée par
1 commentaires
Avis sur Hacker News
J’aurais aimé que l’on traite plus en profondeur les problèmes déjà identifiés de la programmation au moment de la compilation, plutôt que de la louer sans esprit critique
La programmation par étapes n’a rien de nouveau, et la famille Lisp fait des choses similaires depuis des décennies, ce qui a déjà mis en lumière de nombreux compromis de conception et problèmes. Par exemple, ce type de généricité casse la paramétricité (parametricity), ce qui rend l’inférence à partir de la seule signature de type d’une fonction plus difficile. On ne sait pas non plus clairement comment Zig traite les types génériques récursifs, ni à quel moment et dans quel ordre interagissent la vérification de types et le calcul au moment de la compilation. L’article dit que le code de compilation peut générer non seulement des valeurs mais aussi du code, sans toutefois aborder l’hygiène (hygiene). Pour une discussion liée, https://typesanitizer.com/blog/zig-generics.html est un bon texte
En dehors de ça, voir
comptimedans du code Zig me fait parfois penser « ah… ». Comme avec les macros Lisp, on peut facilement s’en servir pour éviter un problème qui n’existerait pas, ou disparaîtrait avec une meilleure structure du code. L’exemple de l’article où l’on parcourt les champs d’une structure pour additionner des valeurs est typique de la manière dont les gens utilisentcomptimeen pratique, alors que dans bien des cas il vaut mieux utiliser une structure de données réellement itérable commestd.enums.EnumArrayLa préoccupation est claire d’un point de vue théorique, mais son importance dans un langage pratique est une autre question. Les templates C++ cassent eux aussi la paramétricité via la spécialisation de templates, sans que cela pose en général de gros problèmes en pratique, et cela permet même des optimisations. Par exemple, une implémentation comme
std::vector<bool>, qui stocke réellement chaque élément sur 1 bit, n’est possible qu’avec ce type de souplesseJe vois le
comptimeà la Zig comme un compromis direct entre faculté d’inférence et expressivité, comparé à des signatures de fonctions définies de manière plus statique. Cela n’affecte que le code qui fait ce genre d’inférence à partir des types, et c’est quelque chose qu’on peut choisir selon les besoins. Quand la signature de type ne suffit pas, lire quelques dizaines de lignes de source m’a généralement permis de comprendre comment l’utiliser, sans rester confus très longtemps. Pour les types génériques récursifs, utiliser un nom en cours de déclaration produit « dependency loop detected », mais il existe des contournements, et l’exemple générique de l’article se référence lui-même. La vérification de types se fait pendantcomptime; par exemple, le premier@compileLog("Hi")s’exécute, mais siconst a: u32 = "42";produit ensuite une erreur de type, on n’atteint pas le second log. Il peut y avoir des problèmes subtils de vérification de types entrecomptimeet l’exécution, mais on ne les voit que dans du code assez ésotérique, ils se résolvent facilement, et l’équipe principale en est consciente, donc on peut espérer que ce sera traité avant la 1.0. Il faudrait aussi expliquer plus précisément ce que signifie exactement l’hygièneSi une fonction a un objectif logique clair, avec un bon nom et des noms de paramètres bien choisis, on peut déjà inférer beaucoup de choses depuis sa déclaration seule. Les labels de paramètres, les noms de types et d’autres éléments que le programmeur peut choisir peuvent aussi être vus comme faisant partie du nom. Je suis d’accord avec la conclusion du titre de l’article lié, mais l’argumentation ressemble plus à un texte de débat cherchant à marquer des points qu’à un raisonnement substantiel. Un meilleur cadrage serait souplesse contre complexité. Un système de génériques figé, s’il est bien conçu, est plus simple qu’un système programmable mais moins flexible. La force de Zig est que son langage de métaprogrammation est presque identique au langage général, et que ce langage général est lui-même simple, ce qui réduit le coût de complexité supplémentaire. En revanche, cela introduit une complexité où le code de compilation et le code d’exécution se mélangent avec peu d’indices, ce qui rend plus difficile pour le programmeur de distinguer ce qui s’exécute, quand. Peut-être qu’un serveur de langage ou un plugin d’éditeur pourrait fournir un « shader comptime » donnant une couleur de fond différente au code
comptimeD’après mon expérience avec Rust, beaucoup de choses que les gens essaient de faire avec les génériques constants seraient plus faciles avec une fonctionnalité comme
comptime. Autoriser de l’arithmétique sur les génériques constants tout en conservant la paramétricité est difficile à implémenter ; et si ce que l’on veut vraiment, c’est par exemple « un trait de fonction de hachage dont la taille de sortie est N », alors abandonner la paramétricité pour ce besoin précis et transformer N en génération de code à une étape antérieure peut être acceptable. Mais les macros Rust sont à la fois trop flexibles et trop pénibles pour être utilisées ainsi. Malgré tout, dès qu’on remplace le polymorphisme paramétrique par une simple fonctionnalité naïve de génération de code, les ennuis commencentD l’a depuis 17 ans. Les fonctionnalités de D migrent régulièrement vers d’autres langages
Zig marque les blocs exécutés pendant la compilation avec le mot-clé
comptime, tandis qu’en D, ce n’est pas un mot-clé mais le fait qu’il s’agisse d’une expression constante qui détermine l’exécution. Par exemple,int s = sum(3, 4);s’exécute à l’exécution, alors queenum e = sum(3, 4);s’exécute au moment de la compilation. En évitant les variables globales non constantes, les entrées/sorties et les appels à des fonctions système commemalloc(), un grand nombre de fonctions peuvent être exécutées à la compilation sans modification. Grâce à la gestion automatique de la mémoire de D, l’allocation mémoire est également possibleAutrefois, il fallait écrire un programme séparé pour créer une table statique, mais l’exécution de fonctions à la compilation a supprimé ce besoin. Les valeurs d’initialisation du tableau
tytabproviennent d’une lambda qui calcule et renvoie le tableau. L’exemple complet se trouve sur https://github.com/dlang/dmd/blob/master/compiler/src/dmd/ba.... Un autre usage courant de la CTFE est la création de DSLSi la machine sur laquelle tourne le compilateur est différente de celle sur laquelle le programme sera exécuté, comment le compilateur D garantit-il la validité du résultat ? Par exemple, je me demande comment le compilateur sait que
int s = sum(100000, 1000000)donnera la même valeur sur toutes les machines x86. Il peut y avoir de subtiles différences selon les générations de CPU ; comment garantit-on qu’un calcul effectué sur l’hôte donnera bien le même résultat sur la machine cible, ou bien suppose-t-on simplement que si l’architecture est la même, l’hôte et la cible sont suffisamment proches ?Avec
int sum(int a, int b) { return a + b; }, on peut écrire quelque chose comme_Static_assert(sum(3, 4) == 7, "look ma, check at compile time!");. Je ne sais pas pourquoi la norme C n’a pas ajouté ça, et ça fonctionne biencomptimesquare(2)est un appel à l’exécution, etcomptime square(3)un appel au moment de la compilation. Un appelcomptimeproduit une erreur de compilation si quelque chose n’est pas compatible avec la compilation, et c’est à mon avis une fonctionnalité importante. C’est un signal d’alerte utile quand un code censé être exécuté à la compilation bascule par inadvertance vers une évaluation à l’exécution à cause d’un changement des arguments d’entréeZig a l’air intéressant, mais j’aimerais qu’il y ait une surcharge d’opérateurs
La plupart des arguments contre la surcharge d’opérateurs ne me convainquent pas vraiment. L’idée qu’on ne sache pas ce qui se passe réellement en interne n’est pas suffisante, puisqu’on peut tout aussi bien faire en sorte qu’une fonction appelée
addeffectue une multiplication.iostreamsde C++ ouboost::spiritsont souvent cités comme exemples d’abus, mais dans d’autres langages qui ont la surcharge d’opérateurs, on voit rarement ce genre de choses, et cela ressemble plutôt à un problème propre à C++En OCaml, on peut redéfinir des opérateurs, mais seulement dans le contexte d’un autre module. Par exemple, si
+a été redéfini dans le moduleVec3, on peut écrireVec3.(a + b + c + d)oulet open Vec3 in a + b + c + d. En lisant le code, on ne se demande jamais « d’où vient ce+? », et c’est bien mieux quea.add(b).add(c).add(d). Cela dit, Zig semble déjà en train de se figer, donc il paraît difficile qu’un grand changement vienne encore résoudre ce problème__de Python et PHP, on peut ignorer toute la magie qui tourne derrièreL’approche de Zig est rafraîchissante, et la capacité à suivre le code est plus importante que les quelques secondes perdues à taper quelques caractères de plus
La solution idéale serait que le langage fournisse des opérateurs définis par l’utilisateur qui rendent explicitement visible la surcharge. Par exemple, avec une forme préfixée ou suffixée comme
let c = a |+| b, la personne qui lit le code sait que l’opération|+|est en réalité un appel de fonction. Cela peut toujours être abusé, mais au moins cela réduit l’une des principales inquiétudesCe serait probablement acceptable si le langage offrait directement une syntaxe de maths vectorielles, comme les langages de shading. Zig a au moins un type
@Vector()un peu similaire à la Vector Extension de Clang, mais malheureusement pas à l’Extended Vector Extension. Voir https://ziglang.org/documentation/master/#Vector et https://clang.llvm.org/docs/LanguageExtensions.html#vectors-...Je ne sais pas s’il existe déjà une proposition en ce sens
fieldNamesressemble beaucoup àfieldPairsde Nim, et c’est une structure extrêmement pratiqueCela rend la sérialisation efficace très facile à mettre en place, et récemment j’ai même utilisé
fieldPairspour implémenter en une vingtaine de lignes une vérification de sûreté des threads à la compilation pour un type. À mon avis, ce genre de fonctionnalité devrait faire partie du socle standard des langages de programmation. C’est l’un des points qui me manquent le plus en Rust, où, la dernière fois que j’ai essayé, on restait limité à des macros non typées. C’est beaucoup trop contraignant. On n’a plus qu’à espérer qu’une struct d’un crate aitserdeimplémenté, et on ne peut pas non plus construire soi-même par programme une struct ayant les mêmes champsC’est vraiment dommage. Des choses comme
serdeseraient bien plus faciles à implémenter avec de la réflexion à la compilationLe seul compromis, c’est qu’il faut inclure ce genre de bibliothèques sous forme de code source
Depuis que j’ai essayé d’écrire une bibliothèque de parsing C à peu près aboutie, je ne suis plus vraiment convaincu par l’idée que le métaprogrammation soit absolument nécessaire au niveau du langage
Si on veut générer des structures, de la sérialisation, des propriétés, de l’instrumentation, etc., il suffit d’écrire un programme C classique qui traite des fichiers source et produit des fichiers source, puis de l’exécuter d’abord dans le script de build. Je me demande d’ailleurs comment les gens déboguent et testent ce genre de méta-programmes. Le mien étant un programme C ordinaire, j’utilise simplement le même débogueur et les mêmes outils que pour le reste du code
Le compilateur lui-même est écrit dans le même langage, donc on peut brancher un plugin qui accède à l’AST en lecture seule et émet du code source C#. Pour le débogage, il faut un peu plus de travail afin de mettre en place un bon framework de test, mais ensuite on peut exécuter le compilateur avec le plugin depuis un framework de tests unitaires standard et l’inspecter avec un débogueur interactif
« Exécution arbitraire au moment de la compilation en C :
cl /nologo /Zi metaprogram.c && metaprogram.exe,cl /nologo /Zi program.c. Le code exécuté à la compilation tourne à vitesse native, peut être débogué, et reste entièrement procédural et arbitraire. Il n’est pas nécessaire que le compilateur exécute le code. » https://x.com/ryanjfleury/status/1875824288487571873En C, pour faire cela, il faut écrire un parseur C complet pour le programme C qui traite le fichier source
J’écrivais du code Python qui générait du code Python, lequel était ensuite recompilé. C’était fragile et affreusement chaotique, et c’est l’une des grandes raisons qui m’ont fait quitter Python. C’est un peu comme demander pourquoi on ne passe pas simplement tous les arguments de fonction sous forme de chaînes. Certaines personnes écrivent du code basé sur des chaînes, mais cela devrait rester rare, et le langage devrait offrir un moyen de l’éviter
Un motif intéressant, c’est la possibilité de générer des structures au moment de la compilation
J’ai expérimenté avec PyTorch en générant un fichier JSON, puis en le lisant via
@embedFiledans Zig, avant de produire une structure avec une méthoderunspécifique pour implémenter un réseau de neurones. En théorie, cela permettrait au compilateur d’optimiser directement le réseau de neurones. Je n’ai pas encore réussi à démontrer de gros bénéfices, mais tout le réseau se trouve sur la pile, sans allocation dynamique. Je ne sais pas si c’est réellement une bonne chosecomptimeJe ne suis pas certain que
comptimedans Zig soit particulièrement plus rapide, et je n’ai pas non plus envie d’exécuter à chaque fois le parseur JSON qui génère la structureC’est intéressant, mais dans ce cas je me retrouve à être d’accord avec les commentaires du genre « D l’a fait avant »
Si
comptimede Zig vous a impressionné, Nim mérite vraiment aussi qu’on s’y intéresse, avec son évaluation de code à la compilation et son système complet de macros sur l’ASTIl a les mêmes problèmes d’écosystème que d’autres langages de niche, avec en plus des mainteneurs controversés, des contributeurs clés qui ne semblent pas rester longtemps, et un financement principalement assuré par une entreprise crypto. Il y a dix ans, ce genre de choses m’aurait moins dérangé, mais aujourd’hui c’est différent
Nim semble aussi vouloir s’éloigner des exceptions, mais les exceptions contaminent les fonctions et obligent à y penser même lorsqu’on n’utilise pas de fonctions qui en lancent[1]. La vie est trop courte pour gérer des flux de contrôle invisibles. [1]: https://github.com/status-im/nim-stew
Dans l’ensemble, Zig est un langage plutôt bon et il fait ce dont on a besoin
Le fait de ne pas s’éloigner de son objectif est très important, et c’est pour cela que je n’aime pas trop voir certains langages utilisés partout simplement parce qu’ils sont populaires
J’aimerais qu’il existe quelque chose qui combine les capacités de métaprogrammation de Zig avec l’énorme écosystème, la communauté et la sûreté de Rust
En termes de conception du langage, Zig me plaît bien davantage que Rust, mais en tant que programmeur amateur assez maladroit, je n’ai pas confiance dans ma capacité à écrire avec Zig quelque chose de réellement utile ou fiable, du moins pour l’instant
Parce que le compilateur ne m’aidait pas à détecter ce genre de problèmes au moment de la compilation. Je sais que Zig ne fait pas cette promesse, mais pour moi c’est un défaut rédhibitoire, donc Zig n’est probablement pas le bon langage. En revanche, le concept de
comptimeme plaît davantage que les macros de Rust