Aller au contenu
learn

1. Installation, v run, modules et projets

Code : examples/l01_hello.v, examples/l01_script.v et le projet l01-project.

C# Java V
Le compilateur produit de l’IL dans une .dll du bytecode dans des fichiers .class du code C, dans un fichier temporaire
Ensuite le CLR compile l’IL à l’exécution (JIT) la JVM interprète et compile le bytecode (JIT) un compilateur C transforme le C en exécutable natif
Pour l’exécuter, il faut le runtime .NET, sauf publication autonome ou Native AOT une JVM, ou une image jlink rien : l’exécutable est natif

V est livré avec un compilateur C, TCC (Tiny C Compiler), dans thirdparty/tcc de chaque archive de release. TCC compile vite et optimise peu : V l’utilise pour les builds de tous les jours, et le compilateur C du système (gcc, clang ou MSVC) pour les builds optimisés avec -prod. v -showcc affiche la commande du compilateur C. La CI du cours montre quel compilateur construit le « Hello World » de v new, et la taille du résultat :

OS v . taille v -prod . taille
Windows (ma machine) tcc.exe 281 600 octets MSVC cl.exe 29 184 octets
Windows (runner CI) tcc.exe 280 576 octets gcc (MinGW) 160 784 octets
Linux (runner CI) tcc.exe 385 782 octets cc (gcc 13.3) 138 296 octets
macOS (runner CI) cc (Apple clang) 96 808 octets cc (Apple clang) 126 408 octets

Sous macOS, V a choisi cc même sans -prod. Sous Windows, -prod utilise le compilateur C qu’il trouve : MSVC sur ma machine, qui a les Visual Studio Build Tools et pas de gcc, et gcc MinGW sur le runner. La leçon 4 montre pourquoi ce choix compte pour la mémoire.

La documentation recommande de construire V depuis les sources avec git clone et make, ce qui suit la branche master. Ce cours fixe plutôt la release 0.5.2, et télécharge son archive : une par OS, avec v, la bibliothèque standard, les outils et TCC. Je l’installe dans un dossier à part et je ne la mets pas dans le PATH global.

Fenêtre de terminal
Invoke-WebRequest https://github.com/vlang/v/releases/download/0.5.2/v_windows.zip -OutFile v_windows.zip
Expand-Archive v_windows.zip -DestinationPath C:\tools # crée C:\tools\v (53 s sur ma machine)
C:\tools\v\v.exe version

Pour l’appeler v depuis n’importe quel terminal, ajoute C:\tools\v à ton PATH, ou exécute v.exe symlink dans un shell administrateur, comme l’explique le README (à vérifier : je ne l’ai pas exécuté).

Ensuite, sur n’importe quel OS :

> v version
V 0.5.2 7647ce1

v doctor affiche ce que V a trouvé : son dossier, le dossier VMODULES où vont les packages, et chaque compilateur C. Sur ma machine :

|V full version |V 0.5.2 45ae01d23168b6372f734eeb38a77360bbcf184a.7647ce1
|V executable |C:\Users\spare\tools\v-0.5.2\v.exe
|VMODULES |OK, value: C:\Users\spare\.vmodules
|cc version |N/A
|gcc version |N/A
|clang version |N/A
|msvc version |N/A
|tcc version |tcc version 0.9.27 (x86_64 Windows)

v doctor indique msvc version N/A, et pourtant v -prod a trouvé MSVC via l’installation des Build Tools.

examples/l01_hello.v
fn main() {
println('Hello, world!')
}
> v run examples/l01_hello.v
Hello, world!
C# Java V
Compiler et exécuter un fichier dotnet run app.cs (.NET 10) java Hello.java v run hello.v
Compiler seulement dotnet build javac Hello.java v hello.v, qui écrit hello.exe ou hello
Build optimisé dotnet publish -c Release — v -prod hello.v

v run compile vers un exécutable temporaire, l’exécute, puis le supprime. Les options du compilateur se placent avant run ; tout ce qui suit le nom du fichier va à ton programme.

  • fn déclare une fonction ; le type de retour, s’il y en a un, vient après les paramètres.
  • println est une fonction intégrée : pas d’import, pas de Console. ni de System.out..
  • Les chaînes utilisent des guillemets simples ; v fmt transforme les guillemets doubles en guillemets simples.
  • Il n’y a pas de points-virgules, et le code V est indenté avec des tabulations.

Un fichier unique peut se passer de fn main, comme les instructions de niveau supérieur en C# ou un fichier source compact en Java 25 :

examples/l01_script.v
fn greeting(name string) string {
return 'Hello, ${name}!'
}
println(greeting('C#'))
println(greeting('Java'))
Hello, C#!
Hello, Java!

${…} interpole une expression dans une chaîne, comme $"…{name}" en C#. L’ordre est l’inverse de celui de C#, où les instructions de niveau supérieur doivent venir avant les déclarations de types : dans un script V, les définitions viennent en premier, et une fonction placée après la première instruction est rejetée.

fn greeting(name string) string {
return 'Hello, ${name}!'
}
println(greeting('C#'))
fn farewell(name string) string {
return 'Bye, ${name}!'
}
e01_script_order.v:5:1: notice: script mode started here
3 | }
4 |
5 | println(greeting('C#'))
| ~~~~~~~
6 |
7 | fn farewell(name string) string {
e01_script_order.v:7:4: error: all definitions must occur before code in script mode
5 | println(greeting('C#'))
6 |
7 | fn farewell(name string) string {
| ~~~~~~~~
8 | return 'Bye, ${name}!'
9 | }

Le compilateur affiche les erreurs sur la sortie d’erreur standard, avec le fichier, la ligne et la colonne, et se termine avec le code 1.

Fenêtre de terminal
v new hello

v new pose trois questions (description, version, licence) et crée un dossier :

hello/
├── .editorconfig
├── .gitattributes
├── .gitignore
├── main.v ← module main, fn main
└── v.mod ← like .csproj or pom.xml, but only metadata
Module {
name: 'hello'
description: 'Greets people'
version: '0.0.0'
license: 'MIT'
dependencies: []
}

Il exécute aussi git init quand Git est installé. v init fait la même chose dans un dossier existant.

Les réponses sont lues sur l’entrée standard : si elle est fermée, comme dans un script sans entrée, v new ne s’arrête pas, et écrit '<EOF>' dans les trois champs. os.input renvoie cette chaîne à la fin de l’entrée (os.v, lignes 436-441), et vcreate.v ne remplace par la valeur par défaut qu’une réponse vide. La CI répond avec printf 'Greets people\n\n\n' | v new hello.

v run . compile tous les fichiers .v du dossier en un seul programme, qui porte le nom du dossier :

> cd hello
> v run .
Hello World!

Il n’y a pas de fichier de build à maintenir : V trouve les fichiers et les modules à partir des dossiers. v.mod nomme le projet, liste ses dépendances, et marque la racine à partir de laquelle import trouve les modules du projet.

Un module est un dossier. Chaque fichier .v du dossier commence par module <nom du dossier>, et ils voient tous les déclarations les uns des autres ; les fichiers à la racine du projet sont module main.

l01-project/
├── v.mod
├── main.v
└── greet/
└── greet.v
greet/greet.v
module greet
// hello est publique : les autres modules peuvent l'appeler sous la forme `greet.hello`
pub fn hello(name string) string {
return 'Hello, ${capitalize(name)}!'
}
// capitalize est privée au module, comme `internal` en C# ou package-private en Java
fn capitalize(s string) string {
return s.capitalize()
}
main.v
module main
import os
import greet
fn main() {
names := if os.args.len > 1 { os.args[1..] } else { ['world'] }
for name in names {
println(greet.hello(name))
}
}
> v run .
Hello, World!
> v run . ada grace
Hello, Ada!
Hello, Grace!

os.args contient le chemin du programme, puis ses arguments : os.args[1..] saute le chemin, comme string[] args en C# et en Java.

C# Java V
Unité namespace, indépendant des dossiers package, un dossier module, un dossier
Import using Greet; import greet.*; import greet
Appel Greeter.Hello(…) Greeter.hello(…) greet.hello(…) : toujours avec le nom du module
Public public public pub
Par défaut internal (l’assembly) pour un type, private pour un membre package-private privé au module

Sans pub, un autre module ne peut pas appeler la fonction :

module main
import greet
fn main() {
println(greet.capitalize('java'))
}
main.v:6:16: error: function `greet.capitalize` is private
4 |
5 | fn main() {
6 | println(greet.capitalize('java'))
| ~~~~~~~~~~~~~~~~~~
7 | }

Deux autres règles de la documentation des modules : les imports ne peuvent pas être circulaires, et un module dans a/b/ s’importe sous la forme a.b mais s’appelle par son dernier nom, b.hello().

Tâche V dotnet Java
Nouvelle application v new app dotnet new console mvn archetype:generate
Exécuter un dossier v run . dotnet run mvn exec:java
Compiler v . dotnet build mvn compile
Build optimisé v -prod . dotnet publish -c Release mvn package
Tester v test . dotnet test mvn test
Formater v fmt -w . dotnet format un plugin comme Spotless
Linter v vet . analyseurs Roslyn Error Prone
Vérifier l’installation v doctor dotnet --info java -XshowSettings

v help <command> documente chaque commande. Les tests, v fmt, v vet et v doc sont le sujet de la leçon 8, les packages de la leçon 12.

  • V traduit ton programme en C, et un compilateur C produit l’exécutable : TCC, livré avec V, pour les builds de tous les jours ; gcc, clang ou MSVC avec -prod.
  • v run file.v ou v run . compile et exécute ; v file.v ou v . compile seulement.
  • Un fichier unique peut se passer de fn main, si chaque définition vient avant la première instruction.
  • Un module est un dossier ; une fonction est privée à son module sauf si elle est pub, et elle s’appelle toujours avec le nom du module.
  • v.mod contient les métadonnées du projet ; il n’y a pas de liste de fichiers à maintenir.
  1. Déplace fn greeting de l01_script.v sous les deux appels à println. Que dit le compilateur, et pourquoi le même ordre ne fonctionne-t-il pas dans un script ?
Solution
error: all definitions must occur before code in script mode

Dans un fichier sans fn main, V crée un main implicite de la première instruction jusqu’à la fin du fichier (mode script) : une fonction déclarée après ce point se trouverait à l’intérieur de main. Dans un fichier avec fn main, l’ordre n’a pas d’importance : V remonte (hoisting) chaque déclaration.

  1. Ajoute un module stats à une copie de l01-project, avec une fonction publique word_count(s string) int, et affiche le nombre de mots des arguments du programme.
Solution

l01-exercise :

stats/stats.v
module stats
// word_count compte les mots séparés par des espaces
pub fn word_count(s string) int {
return s.fields().len
}
main.v
module main
import os
import greet
import stats
fn main() {
text := os.args[1..].join(' ')
println(greet.hello('reader'))
println('${stats.word_count(text)} words')
}
> v run . V for C# and Java developers
Hello, Reader!
6 words

Rien d’autre ne change : pas de fichier de projet à modifier, import stats trouve le dossier à côté de v.mod. fields découpe sur les blancs.

  1. Construis l01-project avec v -showcc . et avec v -showcc -prod .. Quel compilateur C chaque build utilise-t-il sur ta machine, et quelle est la taille des deux exécutables ?
Solution

La première ligne qui commence par > C compiler cmd: nomme le compilateur. Sous Windows et Linux, le premier build utilise thirdparty/tcc/tcc.exe ; le build -prod utilise gcc, cc ou MSVC, selon ce que V trouve. Sous macOS, les deux utilisent cc. Compare tes tailles avec le tableau en haut de cette leçon : la taille dépend autant du compilateur C que de -prod, puisque sur le runner macOS c’est le build -prod qui est le plus gros.