Aller au contenu
learn

Journal

  • V 0.5.2 installé sous Windows, et en CI sous Linux, Windows et macOS
  • CI : exemples, snippets rejetés et panics comparés à leur sortie attendue sur trois OS
  • Leçon 1 : installation, v run, modules et projets
  • Leçon 2 : types, variables immuables, structs et méthodes
  • Leçon 3 : erreurs, ?, ! et or { }
  • Leçon 4 : tableaux, maps, slices et mémoire
  • Leçon 5 : interfaces et génériques
  • Leçon 6 : enums, types somme et match
  • Leçon 7 : concurrence, spawn, canaux et shared
  • Leçon 8 : tests, v fmt, v vet et v doc
  • Leçon 9 : appeler du C

V 0.5.2 au tag 7647ce1. Plusieurs lignes opposent la documentation à la version installée, et l’une d’elles est un compilateur qui envoie votre source à un serveur tant qu’on ne connaît pas la variable d’environnement. Rien n’a été signalé : le journal le dit franchement, et précise que le tracker n’a même jamais été fouillé.

Il n’y a pas de tableau d’expériences : aucune entrée de ce journal ne pose d’hypothèse avant la mesure qui l’éprouve. Les deux comparaisons construites — les trois régimes de mémoire, et vingt exécutions répétées d’une course de données — sont des mesures sans a priori consigné, et elles restent dans leurs entrées datées.

Attendu Ce qui se passe Où Mesure État
Le compilateur n’envoie rien nulle part V soumet par défaut les erreurs du compilateur C à un serveur. Le rapport emporte le C généré fautif et les lignes de source V, avec les chemins de la machine de l’auteur vlib/v/builder/c_error_report.v 485-506, V 0.5.2 Sent C compiler bug report to https://bugs.vlang.io/bug-report., avec un identifiant de rapport et une commande curl -X DELETE pour l’effacer. Il envoie sauf si V_C_ERROR_BUG_REPORT_DISABLED est posé, et s’abstient dans la CI GitHub Reproduit. La seule mention trouvée est dans v help build-c, pas dans le docs.md de la 0.5.2. check.sh et les exécutions locales posent désormais la variable 2026-09-14
-autofree libère l’essentiel et le ramasse-miettes fait le reste, comme le dit la documentation -autofree éteint complètement le ramasse-miettes : ce qu’autofree rate fuit donc vlib/v/pref/pref.v 807-811 gc_is_enabled() rend false Reproduit ; la documentation et le code se contredisent 2026-09-14
-prod change l’optimisation, pas la gestion mémoire -prod avec MSVC sous Windows et sans option -gc bascule en no_gc, règle que l’auteur n’a trouvée documentée nulle part vlib/v/pref/default.v 369-381 Après 50 tours : 20 Mo avec v run, 542 Mo avec v -prod run, 30 Mo avec v -prod -gc boehm run Reproduit ; l’exécuteur Windows de la CI utilise gcc pour -prod et ne le montre donc jamais 2026-09-14
Une fonction anonyme dans un lock compile et s’exécute V l’accepte, puis TCC échoue : le C généré de la fonction anonyme contient le déverrouillage du bloc englobant génération de code, V 0.5.2 'usage' undeclared, venu de sync__RwMutex_runlock(&usage->mtx);return _t1;. Se produit avec lock comme avec rlock, avec ou sans capture Reproduit ; épinglé en CI sous compiler_bugs/b07_return_in_lock.v pour qu’un correctif se remarque. Le tracker de V n’a jamais été fouillé, rien n’a été signalé 2026-09-14
Pousser dans un canal fermé panique, comme le dit la documentation ch <- x sur un canal fermé ne fait rien : le pushval généré appelle try_push_priv et ignore le .closed rendu vlib/v/gen/c/cgen.v 2492-2501 popval rend de même une valeur nulle sur un canal fermé et vide. Avec or, les deux signalent channel closed Reproduit, non signalé 2026-09-14
Accéder à un champ que n’a qu’une variante d’un type somme est une erreur de compilation Cela compile en conversion implicite et panique à l’exécution vlib/v/checker/checker.v 3180-3201 as cast: cannot cast main.Floating to main.Release Reproduit ; vérifié en CI par panics/p06_variant_field.v 2026-09-14
Un littéral flottant divisé par un entier devient f64 7.0 / 2 garde le type float literal vlib/v/ast/types.v 1278 typeof(y).name affiche float literal là où z := 3.5 donne f64 ; println(y) affiche pourtant 3.5 Reproduit 2026-09-14
Convertir une chaîne en nombre signale l’échec Cela n’échoue jamais string.int(), V 0.5.2 '12abc'.int() vaut 12, 'abc'.int() vaut 0, '99999999999'.int() vaut 2147483647. strconv.atoi signale les trois Par conception, et en silence 2026-09-14
v new sans entrée s’arrête, ou prend ses valeurs par défaut Il écrit '<EOF>' dans v.mod comme description, version et licence vlib/os/os.v 436-441 avec cmd/tools/vcreate/vcreate.v 170-181 v new hello < /dev/null. L’outil ne remplace par son défaut qu’une réponse vide, et '<EOF>' n’est pas vide Reproduit ; la CI contourne en envoyant trois réponses 2026-09-14
Aliaser un tableau et le trancher se comportent pareil alias := original partage en silence la mémoire d’un tableau mut ; first := lines[..2] sur le même tableau est cloné, avec un avis vlib/v/checker/assign.v 845-855 Le vérificateur ne cherche que les tranches Reproduit ; la leçon 4 montre les deux 2026-09-14
Deux fonctions de même nom signalent toujours la redéfinition L’erreur dépend du site d’appel ordre entre vérificateur et constructeur, V 0.5.2 describe('Mission') donne builder error: redefinition of function describe ; describe(474) ne donne que cannot use int literal as string in argument 1 to describe Reproduit 2026-09-14
fn [mut x] capture par référence spawn fn [mut total] (n int) { total += n }(i) dans une boucle compile et laisse total à 0 capture de closure, V 0.5.2 Le seul indice est warning: unused variable: total sur la liste de capture Reproduit 2026-09-14
v test affiche la même chose en local et en CI Avec CI ou GITHUB_JOB posé, il masque les lignes OK sauf si VTEST_HIDE_OK=0 cmd/tools/modules/testing/common.v 37-43 Le premier passage en CI de la leçon 8 a échoué sur les trois OS à cause de cela Reproduit 2026-09-14
Les nombres d’un rapport de test décrivent les données Left value (len: 6): [9, 5] donne la longueur du texte affiché, et le résumé compte des assertions, pas des fonctions de test v test -stats, V 0.5.2 3 failed, 3 passed, 6 total pour trois fonctions. Une fonction marquée @[assert_continues] dont les assertions ont échoué est listée OK Reproduit ; le code de sortie, lui, est juste 2026-09-14
v doctor indique le compilateur C qui sera employé Pas toujours v doctor contre v -showcc, V 0.5.2 v doctor dit msvc version N/A tandis que v -showcc -prod compile avec cl.exe. Sous macOS, v doctor liste TCC alors que v -showcc montre cc Reproduit 2026-09-14
  • 0.5.2 est la dernière release, publiée le 2026-07-12 ; son tag pointe vers le commit 7647ce1. La dernière release hebdomadaire est weekly.2026.08, de février.
  • Windows : v_windows.zip fait 23 745 972 octets. Je l’ai décompressé dans C:\Users\spare\tools\v-0.5.2, hors du PATH. v version affiche V 0.5.2 7647ce1 ; v doctor affiche V 0.5.2 45ae01d23168b6372f734eeb38a77360bbcf184a.7647ce1. J’ai aussi essayé les commandes de la leçon 1 dans un dossier de test : Invoke-WebRequest a téléchargé l’archive en 3 s, Expand-Archive a mis 53 s à la décompresser.
  • Les trois archives (v_windows.zip, v_linux.zip, v_macos_arm64.zip) contiennent toutes TCC sous le nom thirdparty/tcc/tcc.exe, avec le .exe sous Linux et macOS aussi, et conservent les bits d’exécution de v et de tcc.exe.
  • Le premier v run hello.v a compilé et s’est exécuté en 1,6 s, avec TCC et aucun autre compilateur C installé.
  • docs.vlang.io suit master, pas la release. Sa page The default compiler n’est pas dans le docs.md de 0.5.2, et elle n’est même pas d’accord avec master : la page web (générée depuis le commit 5db92f0) dit que l’exécutable v “contains only the experimental V3 C compiler”, alors que doc/docs.md sur master dit désormais qu’il “contains the default compiler whose source lives in vlib/v”. Les leçons vérifient chaque comportement avec 0.5.2 elle-même.
  • v-examples.yml télécharge l’archive de release de chaque OS, ajoute son dossier à GITHUB_PATH, et exécute check.sh. Le premier push est passé sur les trois OS : les sorties et les messages du compilateur capturés sous Windows sont identiques sous Linux et macOS.
  • check.sh compare la sortie standard et la sortie d’erreur de chaque exemple, donc un nouvel avertissement ou une nouvelle notice fait échouer le job. Les snippets rejetés sont compilés depuis leur dossier, pour que les messages affichent e02_immutable.v:3:2 plutôt qu’un chemin qui différerait d’un OS à l’autre.
  • Un panic affiche son message, puis v hash, les identifiants du processus et du thread, et une backtrace dont les lignes dépendent de l’OS et du compilateur C. check.sh compare la première ligne et le code de sortie, 1 sur les trois OS.
  • v fmt -verify échoue sur les snippets rejetés, parce qu’il en vérifie les types : check.sh formate tous les dossiers sauf compile_fail.
  • Compilateurs C, d’après v -showcc dans la CI : TCC pour les builds de développement sous Windows et Linux ; cc (Apple clang 21) pour les builds de développement sous macOS, bien que v doctor y liste aussi TCC ; gcc (MinGW 15.2) pour -prod sur le runner Windows, cc (gcc 13.3) sous Linux, cc sous macOS.
  • Sur ma machine, v doctor indique msvc version N/A, et pourtant v -showcc -prod compile avec le cl.exe des Visual Studio 2022 Build Tools.

2026-09-14 — Surprises en écrivant les leçons 1-4

Section intitulée « 2026-09-14 — Surprises en écrivant les leçons 1-4 »

v new sans entrée écrit <EOF> dans v.mod. Avec l’entrée standard fermée (v new hello < /dev/null), v new ne s’arrête pas et écrit description: '<EOF>', version: '<EOF>' et license: '<EOF>'. os.input renvoie '<EOF>' à la fin de l’entrée, et vcreate.v ne remplace par la valeur par défaut qu’une chaîne vide. La CI envoie trois réponses par un pipe.

-autofree désactive le ramasse-miettes. La documentation dit qu’autofree libère la plupart des objets et que “the remaining small percentage of objects is freed via GC”. En 0.5.2, pref.v, lignes 807-811 fixe gc_mode = .no_gc pour -autofree, et gc_is_enabled() renvoie false dans l’exemple de la leçon 4. Ce qu’autofree manque fuit.

-prod avec MSVC désactive le ramasse-miettes. default.v, lignes 369-381 passe à no_gc quand le compilateur C est MSVC sous Windows et qu’aucune option -gc n’est donnée. Sur ma machine, v -prod run examples/l04_memory.v affiche GC enabled: false et atteint 542 Mo après 50 tours, contre 20 Mo pour v run ; v -prod -gc boehm run reste à 30 Mo. Le runner Windows utilise gcc pour -prod, et ne le montre pas. Je n’ai pas trouvé cette règle dans la documentation.

Un tableau entier est partagé, une slice est clonée. alias := original partage la mémoire d’un tableau mut sans notice, jusqu’à ce que original dépasse sa capacité ; first := lines[..2] sur le même tableau est cloné, avec une notice. Le checker ne s’intéresse qu’aux slices (assign.v, lignes 845-855). La leçon 4 montre les deux.

Deux fonctions du même nom : l’erreur dépend de l’appel. Avec fn describe(lines int) et fn describe(title string), appeler describe('Mission') donne builder error: redefinition of function describe, l’erreur de la leçon 2. Appeler plutôt describe(474) donne seulement cannot use int literal as string in argument 1 to describe : le checker signale l’erreur de type, et le build s’arrête avant que le doublon ne soit signalé.

Le type de 7.0 / 2 est float literal. La documentation dit qu’un littéral flottant devient un f64 quand son type doit être fixé. Pourtant :

y := 7.0 / 2
println(typeof(y).name) // float literal
z := 3.5
println(typeof(z).name) // f64

L’expression garde le type du littéral, que nomme types.v, ligne 1278. println(y) affiche quand même 3.5.

Une erreur ayant subi un smart cast s’interpole comme une struct. Après if err is ParseError, ${err} affiche toute la struct au lieu de msg() :

line_no 7
interpolated: &ParseError{
Error: Error{}
line_no: 7
}

Sans le cast, ${err} sur un IError affiche le message, et error_with_code('negative', 99) affiche negative; code: 99. La leçon 3 appelle err.msg() explicitement.

Un type d’erreur privé peut être testé depuis un autre module. csv.EndOfFileError n’a pas de pub (reader.v, lignes 25-31), et pourtant err is csv.EndOfFileError compile dans module main.

Une option peut être stockée sans être déballée. z := find_course('java') compile, et println(z) affiche Option(none). Le compilateur ne se plaint que lorsque z est utilisée comme une string, comme dans le name.to_upper() de la leçon 3.

main ne peut pas renvoyer d’erreur. return err dans un bloc or de main donne unexpected argument, current function does not return anything ; l’exemple de la leçon 3 utilise panic(err).

La conversion de chaîne en nombre n’échoue jamais. '12abc'.int() vaut 12, 'abc'.int() vaut 0, '99999999999'.int() vaut 2147483647. strconv.atoi signale les trois.

Une expression de tri ne voit pas les variables locales. courses.sort(lines[a] > lines[b]) donne quatre erreurs, en commençant par can not access external variable lines ; la troisième et la quatrième révèlent que a et b sont des &string.

Une erreur, deux messages. Une erreur de type dans println(…) est suivie de println can not print void expressions sur la même ligne (leçons 2 et 3).

Le parseur CSV naïf échoue sur 50 lignes. pages.csv met entre guillemets les titres qui contiennent des virgules : line.split(',') donne 6 champs sur 50 des 319 lignes. Un script awk sur le même fichier confirme les nombres de la leçon 4 : 117 pages en anglais, 101 en français et 101 en espagnol, et les lignes anglaises par cours.

2026-09-14 — Leçons 5-8 : le plan et les données

Section intitulée « 2026-09-14 — Leçons 5-8 : le plan et les données »
  • La leçon 8 était « tests, v fmt, v vet, docs et packages ». Les tests, les trois outils et leurs bizarreries en CI ont rempli une leçon ; les packages (v install, vpm) passent à la leçon 12, à côté de la compilation croisée et du déploiement.
  • Les leçons 5 à 8 utilisent les projets .NET de GuitarAlchemist/ga au commit a26a7893, copiés du cours LadybugDB dans data/ga. Aucun champ ne contient de virgule, donc split(',') suffit.
  • check.sh affiche désormais sans les comparer les lignes d’un exemple qui commencent par # , comme le fait le cours LadybugDB pour sa leçon 8 : des durées, des nombres de CPU, le résultat d’une data race. Les runners de CI signalent 4 CPU logiques sous Linux et Windows, 3 sous macOS.
  • Le premier push du code de la leçon 8 a échoué sur les trois OS, pour la même raison : v test masque les lignes OK en CI (voir plus bas).

Ce sont des observations sur ga à a26a7893, à l’appréciation de ses mainteneurs ; rien n’a été signalé en amont.

  • Deux versions flottantes. Apps/demerzel-bridge/DemerzelBridge/DemerzelBridge.csproj référence ModelContextProtocol en version 0.*, et Experiments/React/ReactApp1.Server/ReactApp1.Server.csproj référence Microsoft.AspNetCore.SpaProxy en version 8.*-*, préversions comprises. Sans fichier de verrouillage NuGet, deux restaurations du même commit peuvent choisir des packages différents (leçon 6).
  • Une version en quatre parties. GaCLI/GaCLI.csproj référence Microsoft.KernelMemory.Core 0.91.241101.1 : valide pour NuGet, mais la seule des 478 versions qui ne soit pas major.minor.patch avec une étiquette optionnelle.
  • Deux paires de noms similaires. Deux projets s’appellent GaApi.Tests (Tests/Apps/GaApi.Tests et Tests/GaApi.Tests, le cours LadybugDB les a trouvés aussi), et deux ne diffèrent que par la casse : GaCLI/GaCLI.csproj (C#, absent de la solution) et Apps/GaCli/GaCli.fsproj (F#). Une map indexée par nom, comme dans la leçon 5, perd un GaApi.Tests ; avec une clé insensible à la casse, elle fusionnerait aussi les deux CLI.
  • Six projets web hors de la solution. FloorManager, GA.DocumentProcessing.Service, ScenesService, GA.Business.AI, FretboardExplorer et GA.WebBlazorApp utilisent Microsoft.NET.Sdk.Web et ne sont pas listés dans AllProjects.slnx (leçon 6). GA.Business.AI est pourtant référencé par des projets de la solution.

2026-09-14 — Surprises en écrivant les leçons 5-8

Section intitulée « 2026-09-14 — Surprises en écrivant les leçons 5-8 »

V envoie par défaut les erreurs du compilateur C à un serveur. Pendant que j’écrivais la leçon 7, un programme a rencontré le bug du compilateur ci-dessous, et V a affiché Sent C compiler bug report to https://bugs.vlang.io/bug-report., avec un identifiant de rapport et une commande curl -X DELETE. Le rapport contient le C généré fautif et les lignes de source V, avec des chemins de ma machine. c_error_report.v, lignes 485-506 l’envoie sauf si V_C_ERROR_BUG_REPORT_DISABLED est définie, et s’en abstient dans la CI GitHub. La seule mention que j’ai trouvée est dans v help build-c, pas dans le docs.md de 0.5.2. Le check.sh du cours définit la variable, et mes exécutions locales aussi depuis.

Une fonction anonyme avec return à l’intérieur de lock génère du C invalide. V 0.5.2 accepte :

shared usage := Usage{}
rlock usage {
count := fn () int {
return 1
}
println(count())
}

puis TCC échoue avec 'usage' undeclared : le C généré de la fonction anonyme contient sync__RwMutex_runlock(&usage->mtx);return _t1;, le déverrouillage du bloc englobant. Cela arrive aussi avec lock, et que usage soit capturée ou non. compiler_bugs/b07_return_in_lock.v est vérifié en CI, donc le cours remarquera quand une version le corrigera. Je n’ai pas cherché ce bug dans les issues de V, ni ne l’ai signalé.

Une méthode d’interface mut: accepte un receveur immuable. La documentation dit qu’avec fn (s MyStruct) write(a string) string, MyStruct “implements the interface Foo, but not interface Bar”, où Bar déclare write sous mut:. L’exemple même de la documentation, avec fn fn2(mut s Bar) décommenté et appelé avec fn2(mut s2), compile et affiche Bar. table.v, lignes 280-294 ne rejette que l’inverse, un receveur mut pour une méthode d’interface immuable. Le Tally.total() de la leçon 5 s’appuie dessus.

Un champ que seul un variant possède est un as implicite. Sur type Version = Floating | Release, v.parts compile bien que seul Release ait parts, et déclenche un panic as cast: cannot cast main.Floating to main.Release quand v contient un Floating. checker.v, lignes 3180-3201 réécrit l’accès au champ en AsCast. La CI le vérifie avec panics/p06_variant_field.v.

Le smart cast d’un type somme mut n’a pas besoin de mut. La documentation dit que pour une variable mutable, if mut w is Mars est obligatoire, “otherwise w would keep its original type”. Avec mut v := Version(Release{[9, 5, 1]}), if v is Release { println(v.major()) }, où major est une méthode de Release seulement, compile et affiche 9. Je n’ai pas cherché le cas contre lequel la règle protège.

L’erreur d’une fonction générique ne nomme pas l’appel. Avec largest[T] appelée sur une struct sans <, la seule erreur est cannot use > as <= operator method is not defined, sur le > à l’intérieur de la fonction générique (infix.v, lignes 831-833) : ni le type ni la ligne de l’appel n’apparaissent, et <, la méthode à définir, n’est pas nommée.

go est spawn. La documentation présente go comme un thread léger géré par le runtime V. Sans -use-coroutines, le parseur produit un spawn (parser.v, lignes 1391-1405) ; même avec, un go dont le handle est utilisé se rabat sur un thread (spawn_and_go.v, lignes 20-30).

Un canal fermé ne déclenche pas de panic. La documentation dit qu’envoyer sur un canal fermé provoque un panic à l’exécution. ch <- x sur un canal fermé ne fait rien : le pushval généré appelle try_push_priv et ignore le .closed qu’il renvoie, et popval renvoie une valeur zéro sur un canal fermé et vide (cgen.v, lignes 2492-2501). Avec or, les deux opérations signalent channel closed. La leçon 7 montre les quatre cas.

fn [mut x] modifie une copie. Une closure lancée avec spawn fn [mut total] (n int) { total += n }(i) dans une boucle compile, laisse total à 0 dans main, et le seul indice est warning: unused variable: total sur la liste de capture.

Une map écrite par plusieurs threads plante. L’exercice 3 de la leçon 7, le comptage des packages avec une référence mut au lieu de shared : sur 20 exécutions, 8 résultats faux, 4 panics array.get: index out of range, 8 Unhandled Exception. Le compilateur l’accepte, comme le feraient C# et Java.

v test masque les fichiers qui passent en CI. Quand CI ou GITHUB_JOB est définie, common.v, lignes 37-43 masque les lignes OK sauf si VTEST_HIDE_OK=0. La première exécution en CI de la leçon 8 a échoué sur les trois OS à cause de cela.

Les nombres d’un rapport de test. Left value (len: 6): [9, 5] donne la longueur du texte affiché, pas du tableau. Avec -stats, une fonction marquée @[assert_continues] dont les asserts ont échoué est listée comme OK avec 1 assert, et le résumé 3 failed, 3 passed, 6 total compte des asserts, pas des fonctions de test. Le code de sortie est juste : 1.

v doc montre un champ privé. v doc -comments deps omet les fonctions privées, mais affiche struct Graph avec son champ privé mut: refs.

Arithmétique sur les enums, deux messages. sdk < Sdk.web donne only == and != are defined on enum, use an explicit cast to int if needed ; sdk + 1 donne infix expr: cannot use int literal (right expression) as Sdk, qui ne dit pas que + est refusé.

  • v symlink sous Windows, et s’il modifie le PATH de l’utilisateur.
  • Une distribution Linux minimale sans les en-têtes C : TCC trouve-t-il ce dont il a besoin ?
  • macOS : l’attribut de quarantaine sur une archive téléchargée avec un navigateur, et les builds de développement avec TCC au lieu de cc.
  • v -prod -cc gcc sur une machine Windows qui a à la fois MSVC et MinGW.
  • .vvmrc, qui fixe la version du compilateur d’un projet comme global.json : documenté dans le docs.md de 0.5.2, pas encore essayé.
  • go avec -use-coroutines : quels OS le prennent en charge en 0.5.2, et si une instruction go évite alors vraiment un thread de l’OS.
  • La data race sur la map de l’exercice 3 de la leçon 7 sous Linux et macOS, où je n’ai exécuté que la race du compteur (en CI).
  • Si le suivi des issues de V connaît déjà le bug du return à l’intérieur de lock, et le canal fermé qui ne déclenche pas de panic.