URL: https://linuxfr.org/news/a-la-decouverte-du-langage-v Title: À la découverte du langage V Authors: Andréas Livet BAud, palm123, Ysabeau, Stéphane Bortzmeyer, Aldebaran et Jona Date: 2023-08-31T15:11:11+02:00 License: CC By-SA Tags: Score: 3 V est un langage récent (première version libre sortie en 2019) développé initialement par Alex Medvednikov pour ses propres besoins sur le logiciel [volt](https://volt-app.com/). Dans cette dépêche, j'aimerais vous le faire découvrir, et, je l'espère, vous donner le goût d'en découvrir d'avantage. ![le logo du langage V](https://vlang.io/img/v_community_logo.png) ---- [Site officiel du langage](https://vlang.io/) [Github du langage](https://github.com/vlang/v) [Documentation officielle](https://github.com/vlang/v/blob/master/doc/docs.md) [Guide complet](https://docs.vosca.dev/) [Faire joujou avec le langage dans son navigateur](https://play.vlang.io/) ---- ## Introduction J’étais tombé, il y a quelque temps, sur [un article sur developpez.com](https://programmation.developpez.com/actu/267108/Le-langage-de-programmation-V-vient-d-etre-publie-en-open-source-et-semble-ne-pas-tenir-toutes-ses-promesses/) annonçant que le langage V venait d’être Open source. À l’époque, j’avais simplement été étonné par la légèreté et la rapidité annoncée du langage, tout en voyant qu’à peine sorti il était déjà beaucoup critiqué, nous y reviendrons. Puis, je ne sais pas trop pourquoi, un jour je me suis rappelé de ce langage qui semblait vouloir tout réinventer, jusqu’à pouvoir même se passer de la libc. Piqué par la curiosité, j’ai décidé de lui donner une chance. Comme commençait l’excellent [« calendrier de l’avent du code »](https://adventofcode.com/2020/), que je venais de découvrir, je me suis dit que j’allais le réaliser avec V. Les puzzles des différents jours de ce challenge serviront d’exemple. Notez qu’ils ne se suffisent généralement pas à eux-mêmes et ne compileront pas. J’ai cependant préféré mettre des extraits de code réel que des bouts de code de démonstration. Autant que possible, j’ai mis un lien vers le code utilisé. ## Historique C’est, à la base, une sorte de « clone de go » qui génère du C (compilé ensuite en langage machine par un compilateur type tcc ou gcc, voir [cette section pour plus de détail](#toc-un-langage-plus-transpil%C3%A9-que-compil%C3%A9)) qui a vite évolué pour devenir un langage à part entière. Il tente de faire le grand écart entre la facilité d’utilisation d’un langage « haut niveau » tels que Python ou JavaScript et des performances que l’on retrouve généralement avec des langages plus « bas niveau » tels que C/C++ ou Rust. Après plusieurs versions alpha, le langage est aujourd’hui en phase de bêta avec une [v0.4](https://github.com/vlang/v/blob/master/CHANGELOG.md) sortie récemment (la [0.4.1](https://github.com/vlang/v/releases/tag/0.4.1) vient tout juste de sortir). L’objectif étant, comme pour Go, qu’au moment de la sortie de la v1.0 le langage soit considéré comme stable et assure une rétro compatibilité de tous programme écrit depuis la v1.0. Ah, j’allais oublier le plus important : V a une sympathique mascotte depuis quelques années ! ![Mascotte de V](https://vlang.io/img/veasel.png) ## Rapide tour d’horizon Sans vouloir être aussi exhaustif que la [doc officielle](https://github.com/vlang/v/blob/master/doc/docs.md) ou ce [superbe guide](https://docs.vosca.dev/), parcourons ensemble les éléments important du langage. La syntaxe de V est **quasiment identique** à celle de Go avec quelques emprunts à Rust. L’objectif étant de ne pas réinventer la roue, mais -- justement -- permettre aux développeurs d’appréhender ce langage le plus rapidement et sans surprises. C’est une syntaxe de type « C » (avec les accolades), mais très épurée. Je crois que c’est une de mes syntaxes préférée. Très lisible avec peu de mots clés. V n’est pas une révolution, mais s’inspire de beaucoup d’autres langages pour en prendre les meilleurs concepts et les intégrer dans une syntaxe à la fois accessible et pragmatique. Ce qui le fait s’écarter de la « pureté » de langage comme Zig ou Rust, qui préfèrent avoir une syntaxe souvent plus verbeuse, mais plus _exacte_. En résulte un langage aussi expressif que Python, Ruby ou Javascript, mais avec une compilation avant l’exécution (ce n’est pas un langage interprété), un typage fort et des performances proches du C. ### Installation Le langage s’installe facilement. La méthode la plus simple (peut-être la seule actuellement ?) est de cloner le dépôt git : ```bash git clone https://github.com/vlang/v cd v make ``` Le tout compile en quelques secondes grâce à un binaire `tcc` téléchargé pendant le make. Il n’y a même pas de cible `install` dans le makefile, juste un petit argument `symlink` au binaire pour créer un lien symbolique vers le dossier des binaires de votre système (`/usr/local/bin/v` sur les systèmes Unix). ```bash sudo ./v symlink ``` ### Compiler et exécuter du code La commande `run` permet de compiler (générer un code C puis le compiler, voir plus bas) et exécuter du code contenu dans un fichier. Exemple : ``` v run hello_world.v ``` ### Hello World Le traditionnel « Hello world » peut s’écrire de manière simplifiée : ```v println("Hello world") ``` La fonction `main` n’étant pas obligatoire dans les petits programmes. La version plus verbeuse serait : ```v fn main() { println("Hello world") } ``` ### Des variables immuables par défaut Les variables dans V sont immuables par défaut. C’est un choix fort du langage qui s’inspire ici des langages fonctionnels. Autre choix important : il n’est pas possible de déclarer des variables en dehors d’une fonction, seules les constantes peuvent l’être (comme en Rust). La déclaration de variables se fait de manière similaire à Go, avec un opérateur `:=` qui se distingue de l’opérateur d’affectation `=`. Cette différence d’opérateur peut rebuter au premier abord, mais elle permet de ne pas accidentellement affecter une variable existante quand on voulait en créer une. À noter que, contrairement à Rust, le _shadowing_ (redéfinition d’une variable portant le même nom) n’est pas autorisé. Le typage n’est pas nécessaire pour les types de base. ```v fn play_with_variables() { // Nombre immuable en int32 par defaut a := 5 // Interdit a = 4 // unsigned 64 mutable mut departure_values := u64(1) // autorisé departure_values = 2 // string input := '8,13,1,0,18,9' } // les constantes se déclarent en dehors des fonctions avec une instruction // spéciale et peuvent être utilisées dans tout le module et exportée const ( n = 30000000 k = 6 i0 = k - 1 ) ``` ### Des fonctions typées avec plusieurs retours possibles La déclaration de fonctions est quasi identique à celle de Go, avec une petite nuance, le mot clé `fn` est utilisé au lieu de `func` (comme dans Rust). Les types de paramètres sont obligatoires et positionnés à droite du nom (contrairement aux autres langages de type « C »). ```v fn parse_ticket(ticket_str string) []int { return ticket_str.split(',').map(it.int()) } // Il est possible de renvoyer plusieurs valeurs fn get_pos(str_pos string) (int, int, int) { pos := str_pos.split(',').map(it.int()) return pos[0], pos[1], pos[2] } x_pos, y_pos, z_pos := get_pos(str_pos) ``` À noter que, pour l’instant, il n’est pas possible de définir des valeurs par défaut pour les paramètres, ni de nommer les paramètres lors de l’appel de la fonction (comme en Python). Des discussions sont en cours, mais aucune décision n’a été prise. ### Des conditions sans parenthèse Tout comme en Go, la syntaxe des `if/else` se fait sans parenthèse (sauf si nécessaire). ```v // Sans parenthèse if letter_count >= policy_min && letter_count <= policy_max { valid_password_count++ } // Avec if (value >= rule[0] && value <= rule[1]) || (value >= rule[2] && value <= rule[3]) { valid = true } // if/else if if f.ends_with('cm') { return height >= 150 && height <= 193 } else if f.ends_with('in') { return height >= 59 && height <= 76 } ``` ### Uniquement des boucles `for` Comme en Go (et oui syntaxiquement V est très proche de Go), seul le mot clé `for` permet de faire des boucles. Il sert à tout : itérer sur des chaînes de caractères, des tableaux ou des _maps_, boucler un certain nombre de fois, boucler à condition, boucler indéfiniment, etc. ```v // sur une string for l in password { if l == letter { letter_count++ } } // sur des tableaux answers_group := answers_content.split('\n\n') answers := answers_group.map(it.replace('\n', '')) mut yes_count := 0 for group in answers { yes_count += remove_duplicates(group).len } // sur des map for index, line in toboggan_map[slope['down']..toboggan_map.len] { if index % slope['down'] != 0 { continue } x_pos = (x_pos + slope['right']) % line.len if line[x_pos] == `#` { tree_encountered++ } } // En itérant entre 2 écarts (la valeur de droite étant exclue) // "_" n'était pas traité par le compilateur comme une variable non utilisée for _ in 0 .. num_cycles { grid = run_cycle(grid) } // Syntaxe plus classique, parfois utile for i := i0; i + 2 <= n; i++ { // ... } // Boucle infinie for { if program[cursor].executed { break } // ... } ``` ### Des octets et des runes Le type `string` en V est en fait un simple tableau d’octets immuable. C’est très performant, mais si on veut travailler sur des chaînes UTF-8 par exemple, c’est un peu problématique étant donné que certains caractères sont codés sur plusieurs octets. ```v mut s1 := 'toto' assert s1.len == 4 // Important les string sont immuables s1[1] = 'a' // Ne compile pas s2 := '한국/韓國' assert s2.len == 13 // Et oui c'est la taille en octets // L'iteration n'aura pas trop de sens non plus... for b in s2 { println(b) } ``` Heureusement, V permet aussi de travailler à ce niveau-là grâce à la fonction `.runes()`. ```v s2 := '한국/韓國' assert s2.runes().len == 5 for b in s2.runes() { println(b) } ``` [Comme en Go](https://blog.golang.org/strings), V distingue les chaînes de caractère des runes via l’utilisation des accents graves. ```v rocket := `🚀` assert typeof(rocket) == 'rune' str := 'launch 🚀' assert typeof(str) == 'string' ``` J’apprécie vraiment cette distinction, c’est un confort quand on travaille sur les chaînes de caractère. ### Un modèle objet par composition J’ai longtemps cru au paradigme objet avec tous ses beaux concepts : l’héritage, les accesseurs, le polymorphisme, j’en passe et des meilleurs. Il faut dire que mes enseignants nous avaient vendu la programmation objet comme l’outil ultime de conception de code (oui l’UML tout ça)… Comme beaucoup, j’ai déchanté en me cassant les dents avec des arbres d’héritage obscurs et trop complexes, en surchargeant sans m’en rendre compte (certains langages le permettent) des fonctions de la classe mère, en pensant « objet », là où il fallait plutôt penser « données », en écrivant des accesseurs inutiles, etc. Le seul aspect « intéressant » de la programmation orienté objet est pour moi le fait de pouvoir associer des méthodes à une variable composée (disons une structure). C’est plus élégant et plus lisible. Je préfère de loin écrire `a.push(1)` que `array_push(a, 1)` ou `Array.push(a, 1)`. V emprunte [le même modèle « objet » que Go](https://docs.vosca.dev/concepts/structs/overview.html) en permettant d’associer des fonctions à des structures et la composition de structure. De cette manière on peut créer des structures qui « héritent » du fonctionnement d’autres structures, sans risquer de tomber dans les problèmes liés à la verticalité. Plusieurs types peuvent partager une même signature en implémentant une interface. Étonnamment, il y a un mot clé pour définir une interface, mais pas pour l’implémenter. À la différence de Go, les interfaces peuvent contenir des données et pas seulement des méthodes. ### Les `match` un emprunt bien sympa `match` est un mot clé visiblement issu du monde de la programmation fonctionnelle. J’ai découvert ce concept pour la première fois avec [Elm](https://fr.m.wikipedia.org/wiki/Elm_(langage)) (appelé [`case`](https://guide.elm-lang.org/types/pattern_matching.html)), mais il venait très probablement d'[Haskell](https://en.wikibooks.org/wiki/Haskell/Pattern_matching), Rust aussi [le propose](https://doc.rust-lang.org/book/ch18-03-pattern-syntax.html). C’est une sorte de « super switch/case », une syntaxe permettant d’enchaîner des conditions, tout en limitant au maximum le code à écrire. Comparaison de chaîne, d’entier, inclusion d’une valeur dans un rang alphanumérique, type de la donnée, beaucoup des conditions que l’on pourrait écrire avec un `if/else` sont plus simples à écrire avec `match` À la différence d’un _switch/case_, il n’est pas nécessaire d’utiliser l’instruction `break` pour éviter d’aller dans les branches du dessous et comme avec le `default` de _switch/case_, on passe forcément dans une des branches avec ici une instruction `else` obligatoire à la fin. ```v os := 'windows' print('V is running on ') match os { 'darwin' { println('macOS.') } 'linux' { println('Linux.') } else { println(os) } } ``` ### La programmation parallèle sans effort et sans danger V a emprunté à Go le principe du fonctionnement du mot clé `go` pour rendre n’importe quelle fonction parallélisable. Pour l’instant V crée de vrais processus et non les fameuses « goroutines » qui sont, en quelque sorte, des processus allégés pouvant être créés par milliers. C’est pour ça que le mot clé `go` a été remplacé par `spawn` L’implémentation des « goroutines » est prévue dans les futures versions. Il emprunte aussi à Go [la notion de _channel_](https://docs.vosca.dev/concepts/concurrency/channels.html) qui simplifie grandement la communication entre différents processus. ```v import sync import time fn task(id int, duration int, mut wg sync.WaitGroup) { println('task $id begin') time.sleep_ms(duration) println('task $id end') wg.done() } fn main() { mut wg := sync.new_waitgroup() wg.add(3) spawn task(1, 500, mut wg) spawn task(2, 900, mut wg) spawn task(3, 100, mut wg) wg.wait() println('done') } ``` ### Les sum types Les « [sommes de types](https://docs.vosca.dev/concepts/sum-types.html) » sont un concept encore emprunté au monde fonctionnel (en tout cas, je les ai aussi découverts avec Elm) qui, aujourd’hui, se démocratise, on les retrouve notamment dans Rust, Zig et TypeScript (et sans doute dans d’autres langages). L’idée est simple : pouvoir définir des types de données « conditionnels ». Entendez par là, une variable peut avoir « tel **ou** tel type ». Là où les structures sont des types de données « additionnels » (une personne a une chaîne de caractère prénom *et* une chaîne pour son nom), les sommes de type permettent de définir des types de données conditionnelles (exemples tiré de [cet article](https://medium.com/@willkurt/why-sum-types-matter-in-haskell-ba2c1ab4e372), tous les exemples donnés ne sont pas valables en V) : * Un dé peut avoir 6 **ou** 20 faces * Une publication peut avoir un seul auteur (chaîne de caractère) **ou** plusieurs (tableau de chaîne) * Un créateur peut être un artiste **ou** un auteur en fonction du domaine * L’ouverture d’un fichier peut renvoyer le fichier **ou** une erreur * L’accesseur d’un tableau peut renvoyer une donnée **ou** rien si on est hors du tableau ```v // Ici la valeur du token peut soit être une string ou un entier non signé sur 64 bits type TokenValue = string | u64 struct Token { kind TokenKind val TokenValue loc int mut: next &Token = 0 } ``` ### Une gestion d’erreur moderne [Comme Zig](https://zig.news/edyu/zig-if-wtf-is-bool-48hh), V [intègre la notion d’erreur dans le retour](https://docs.vosca.dev/concepts/error-handling/overview.html) d’une fonction via le caractère `!` au début de la déclaration de type ce qui force à gérer l’erreur directement dans la fonction appelante ou à faire remonter l’erreur plus haut. Cette façon de faire évite d'avoir recours aux exceptions. ```v // Notez le `!` devant le type de retour pour spécifier que la fonction peut retourner une erreur fn (r Repo) find_user_by_id(id int) !User { for user in r.users { if user.id == id { return user } } return error('User ${id} not found') } fn main() { repo := Repo{ users: [User{1, 'Andrew'}, User{2, 'Bob'}] } // Le mot clé `or` permet de gérer le cas d'erreur user := repo.find_user_by_id(10) or { User{-1, 'Unknown'} } println(user) // On peut aussi utiliser un if/else if user := repo.find_user_by_id(10) { println(user) } else { // Contrairement à Zig qui permet de « capturer » l’erreur via |err| ici la variable `err` est automatiquement créée // C’est un choix critiquable, mais je trouve que ça force une convention de code println(err) } // On peut aussi décider de propager l’erreur, dans ce cas le programme s’arrêtera vu qu’on est dans la fonction `main` user := repo.find_user_by_id(10)! } ``` V intègre aussi la notion de _retour optionnel_ (Option/Result type, concept encore emprunté à la programmation fonctionnelle il me semble) via le caractère `?` lui aussi à placer au début de la déclaration de type de retour d’une fonction. Cela permet de spécifier qu’une fonction peut renvoyer un résultat potentiellement vide et permet notamment d’éviter d’avoir des pointeurs null. ```v // Ici au lieu d’une erreur on renvoie `none`, notez le `?` devant le type de retour fn (r Repo) find_user_by_id(id int) ?User { for user in r.users { if user.id == id { return user } } return none } ``` Ici aussi le compilateur force le développeur à gérer le cas. Fort heureusement, des sucres syntaxiques bien pratique permettent de gérer facilement les cas où une valeur n’est pas disponible. ### Les génériques À la différence de Go (même si depuis [go les a aussi intégrés finalement](https://go.dev/blog/intro-generics)), [V permet l’écriture de code générique](https://docs.vosca.dev/concepts/generics.html). Pour l’instant, seul un paramètre « _template_ » est pris en compte, mais cette limitation va rapidement être levée. ```v struct Repo[T] { db DB } fn new_repo[T](db DB) Repo[T] { return Repo[T]{db: db} } // This is a generic function. V will generate it for every type it's used with. fn (r Repo[T]) find_by_id(id int) ?T { table_name := T.name // in this example getting the name of the type gives us the table name return r.db.query_one[T]('select * from ${table_name} where id = ?', id) } db := new_db() users_repo := new_repo[User](db) // returns Repo[User] posts_repo := new_repo[Post](db) // returns Repo[Post] user := users_repo.find_by_id(1)? // find_by_id[User] post := posts_repo.find_by_id(1)? // find_by_id[Post] ``` ### Le fonctionnement des modules ### Quelques sucres syntaxiques « magiques » Des sucres syntaxiques « un peu magiques, mais bien pratiques » pour les fonctions `map`, `filter` et `sort` avec des variables locales automatiquement créées : ```v a := [1, 2, 3, 4] b := a.map(it + 1) // it représent l'item courant println(b) // affiche [2, 3, 4, 5] mut c := [2, 4, 3, 1] c.sort(a < b) // a et b réprésente les deux items à comparer println(c) // affiche [1, 2, 3, 4] ``` Ces sucres syntaxiques (appelés "_compiler magic_") sont utilisés à d'autres endroits, notamment dans la bibliothèque SQL (voir plus bas) ## Les grandes caractéristiques du langage ### Un langage plus _transpilé_ que compilé La première chose à savoir sur V est que par défaut, il ne fournit pas un véritable compilateur, mais qu’il s’agit plus d’un [transpileur](https://fr.m.wikipedia.org/wiki/Compilateur_source_%C3%A0_source) vers C. Le code est par la suite compilé via tcc (option par défaut) ou gcc (avec l’argument `-prod`). Bien sûr, vous pouvez utiliser clang aussi. Il existe une fonctionnalité, expérimentale pour l’instant, qui permet la compilation sans passer par du C, mais il semble que le C restera l’option par défaut si l’on veut avoir de bonnes performances et une prise en charge d’un maximum de plateformes. Ce choix de la transpilation peut étonner au premier abord, mais, en réalité je le trouve extrêmement judicieux, il permet : * d’avoir de très bonnes performances sans effort vu qu’on bénéficie de dizaines d’années d’optimisation des compilateurs C existants, * une compatibilité « gratuite » avec toutes les plateformes existantes permettant un usage aussi bien sur nos ordinateurs, que sur ordiphone ou encore dans l’embarqué (j’ai pu par exemple compiler du V sur Nintendo DS sans trop d’effort), * dans le cas de V, une intégration transparente et n’induisant pas un surcoût de performance avec les bibliothèques C existantes. Bien sûr, d’autres langages y ont pensé, on pourra notamment citer [Vala](https://wiki.gnome.org/Projects/Vala) qui transpile une sorte de C# vers C, mais qui est très fortement lié au projet Gnome et ne semble pas avoir décollé en dehors de cet usage. Il y a aussi [nim](https://nim-lang.org/), dont l’approche est très similaire à V. La différence entre ces deux langages est résumée sur [cette page](https://vlang.io/compare) : * nim utilise clang comme compilateur par défaut alors que V se base sur TCC, * Le code C généré par nim n’est pas fait pour être lu par des humains alors que V génère un code C plutôt lisible, * la syntaxe nim est plus proche de Python que de Go, * Nim utilise un ramasse-miette pour gérer la mémoire allouée sur le tas, alors que V vise une approche différente à terme (pour le moment il utilise aussi un ramasse-miette). À noter que le langage [zig](https://ziglang.org/) s’est récemment doté d’un [_backend_ C](https://ziglang.org/download/0.11.0/release-notes.html#C-Backend) On dit du C qu’il est une sorte « d’assembleur glorifié », on peut ainsi dire de V qu’il est une sorte de « C glorifié ». En ce sens, il se rapproche peut-être plus de bibliothèques comme [Cello](http://libcello.org/), tout en poussant le concept plus loin en proposant un vrai compilateur et tout un écosystème qui lui est propre. ### Une ABI compatible avec C Un des très bons choix du langage est d’avoir une ABI compatible avec celle du C. Cela permet de pouvoir très facilement utiliser des bibliothèques C depuis V sans avoir besoin d’écrire du code de compatibilité (_wrapper_) entre les deux langages. C’est un aspect du langage, que l’on retrouve dans zig aussi, que j’apprécie particulièrement, car il ouvre la voie à l’utilisation de tout un tas de bibliothèques fantastiques. Et cela va dans les deux sens, on peut aussi écrire des modules ou bibliothèques en V utilisables dans un programme C. ### Un langage minimaliste Si on devait décrire V avec un seul mot, ce serait sans doute « minimalisme ». Cet état d’esprit ne se retrouve pas que dans sa syntaxe, mais aussi dans la limitation des dépendances utilisées par la bibliothèque standard et dans la légèreté du code généré. On revient aux sources en quelque sorte en limitant les couches d’abstraction (vous me direz, c’est déjà une abstraction du C), tout en ayant une approche moderne. J’aime vraiment cette idée d’avoir un langage permettant de créer tout un panel d’applications facilement, tout en offrant des performances excellentes, une empreinte mémoire minimale et peu de dépendances. Un des principes de V est qu’il ne doit y avoir qu’une seule manière de faire les choses. Par exemple, V ne propose pas une syntaxe spécifique pour les ternaires (comme Go), mais utilise simplement l’instruction `if/else`, le résultat pouvant être utilisé pour affecter une variable. ```v max = if current > max { current } else { max } ``` ### Un langage typé qui compile vite C’est un point que V partage avec Go. Ayant commencé ma carrière professionnelle sur une grosse base de code C++ qui mettait plus de 15min à compiler et ce, malgré une compilation distribuée sur plusieurs machines, je dois avouer que c’est un bonheur de compiler un projet en quelques secondes. En outre, j’ai passé ces dernières années à coder avec des langages interprétés et faiblement typés (JavaScript, Python, PHP, Ruby, etc.) et il faut avouer que la compilation avant exécution, ça évite beaucoup de soucis. Et puis, on se dit c’est cool de ne rien typer, ça rend le code plus lisible, etc. Mais, quand on en vient à devoir rajouter partout des vérifications de type, des « _cast_ » explicites pour s’assurer d’avoir les bons types, je crois que ça signifie que les types sont nécessaires en programmation. D’ailleurs, les quelques langages cités plus haut évoluent tous vers des possibilités plus poussée de typage (PHP 7 et 8, JavaScript avec TypeScript, [Python > 3.5](https://docs.python.org/3/library/typing.html), etc.) On a ainsi le meilleur des deux mondes : * une exécution quasi instantanée du code, * une vérification de l’intégralité du code et pas seulement celui qui est exécuté. C’est un aspect évident pour tous les développeurs, C/C++, Java, C# ou autres, mais qu’il est bon de rappeler. ### Une gestion de la mémoire « basique », mais sans ramasse miette C’est un aspect que je n’ai pas encore creusé, mais V propose une gestion de la mémoire qui me parait assez « basique ». Par défaut, les variables de type scalaires et les structures sont allouées sur la pile. Donc, pour ces variables le compilateur n’a rien à faire, leur mémoire est désallouée à la sortie de leur portée. Les variables de type tableau ou `map` sont allouées sur le tas. Il est aussi possible de forcer l’allocation sur le tas, cela sans mot clé explicite (comme `malloc` en C ou `new` en C++), il suffit juste de déclarer sa variable avec un `&` devant, signifiant que l’on manipule une référence. ```v struct Point { x int y int } // Allocation sur le tas p := &Point{10, 10} // Comme en C++, les références ont la même syntaxe pour l’accès aux attributs println(p.x) ``` Les mécanismes de gestion automatique de la mémoire dans V sont encore balbutiants. Jusqu’à il y a peu, il fallait libérer manuellement toute ressource allouée sur le tas. L’instruction `free` étant considérée depuis le début comme non sécurisée (à utiliser seulement via le mot clé `unsafe`), on comprend que le créateur de V a toujours eu l’intention de rendre la gestion de la mémoire transparente pour l’utilisateur. Depuis quelque temps, un mécanisme de libération automatique de la mémoire commence à être mis en place . Encore sous la forme d’une option à l’heure actuelle (`-autofree`), il permet de ne plus avoir à se soucier de la mémoire et devrait être le mode par défaut dès la prochaine version. Le compilateur utilise une stratégie hybride (appelée `autofree`) qui mélange le rajout d’instructions de libération de la mémoire (`free`) automatiquement lors des cas « faciles » à repérer et du comptage de références dans des cas plus complexes. Il est encore trop tôt pour dire avec précision quel est le surcoût de ce comptage de références, dans quel cas il est utilisé et si cela constituera la seule stratégie utilisée. Ce qu’on peut dire, c’est qu’après plusieurs années, cette technique de gestion automatique de la mémoire n’est pas encore au point et que V intègre par défaut un [ramasse-miette écrit en C](https://www.hboehm.info/gc/). Ce choix rajoute une dépendance non négligeable au projet, mais permet aux concepteurs du langage de se concentrer sur d’autres problématiques. ### Un formateur de code strict Tout comme Go ou Rust, V inclut [un outil de formatage de code très strict](https://docs.vosca.dev/tools/builtin-tools.html#v-fmt) qui rend tout code écrit en V semblable. C’est vraiment une fonctionnalité que j’apprécie énormément dans un langage : on peut se concentrer sur le fond de l’écriture du code. Tout ce qui touche à la mise en forme est gérée automatiquement et on sait qu’on ne peut pas se tromper. Puis, il faut bien avouer que ça évite les débats au sein d’une équipe :). Un autre aspect important des formateurs de code est qu’ils permettent de faire évoluer automatiquement une base de code vers les nouvelles syntaxes du langage. Par exemple, le changement du mot clé `go` vers `spawn` a été traité automatiquement par `v fmt`. On peut noter quelques particularités : * seul le style de nommage « snake_case » **minuscule** est accepté pour les variables et les constantes, * pour l’instant toute ligne vide est supprimée (cela va changer). ### Une bibliothèque standard ambitieuse Le créateur du langage, Alex Medvednikov, est très ambitieux au sujet des bibliothèques qu’il souhaite proposer par défaut. L’envie est que V puisse être utilisé dans tous les domaines : de la programmation système à l’écriture d’application graphiques en passant par les services web. Pour cela, V intègre dans sa bibliothèque standard : * une bibliothèque 2D bas niveau (basée sur [Sokol](https://github.com/floooh/sokol)) * [une bibliothèque ui](https://github.com/vlang/ui) pour faire des interfaces graphiques. Très limitée (et buguée) pour l’instant, mais les ambitions sont d’être pleinement multiplate-forme par défaut, avec notamment une prise en charge d'iOS et Android prévue, * un serveur web intégré, * une bibliothèque d’accès aux bases de données SQL intégrant la possibilité de coder ses requêtes dans une sorte de SQL, cela grâce aux sucres syntaxiques mentionnés plus haut. Pour l’instant, il faut l’avouer, tout est dans un état peu avancé. À croire que certaines des bibliothèques ont été commencées sans forcément être développées assidûment. Nous reviendrons en détail sur les critiques que l’on peut porter à V (car il y en a bien sûr). Ce que j’apprécie c’est l’intention derrière : fournir une bibliothèque standard répondant à la plupart des besoins tout en étant légère et moderne. Un peu à l’instar de python, mais je l’espère avec des outils d’interface graphique plus modernes que Tk. ## Questions en vrac sur le langage ### Qu’apporte V par rapport à Go ? Ce sont des langages très similaires qui rentrent dans la même catégorie, à savoir des langages modernes, compilés et très facile à prendre en main. Maintenant, si vous êtes frustré par certains manques dans la syntaxe de Go (notamment la gestion d’erreur) ou que vous souhaitez avoir des binaires plus petits et des performances au plus proche du C, V peut être intéressant. Bien entendu, étant en période d’intense développement, il est encore proscrit pour un usage professionnel. De plus, son écosystème est bien moins développé que Go. On pourrait alors se demander pourquoi avoir fait un énième langage ? Je ne suis pas dans la tête d’Alex, mais je pense qu’il aimait vraiment Go et voulait créer un langage encore plus minimaliste tout en apportant des idées intéressantes provenant d’autres langages. Puis, peut-être, tout simplement pour en apprendre davantage sur les langages de programmation. Et personnellement, j’adore le résultat ! ### Est-ce que V est aussi sécurisé que Rust ? V est décrit comme étant « safe », mais je ne crois pas que le compilateur aille aussi loin que celui de Rust. #### Gestion de la mémoire Un des point fort de Rust est la notion de « possession » (_ownership_) d’une donnée allouée sur le tas. En effet, seule une variable à la fois peut posséder une donnée allouée, et quand celle-ci sort de sa portée (scope), elle libère automatiquement la mémoire allouée. Ce mécanisme très abouti permet de garantir que les ressources allouées seront toujours libérées et qu’il n’y aura de problème d’allocation multiple, ou d’accès hors de la mémoire, même en cas de programmation parallèle. V me paraît sur ce point, moins *précis* que Rust et la stratégie d’allocation globale du langage n’est pas clairement définie. Par exemple, dans la documentation, on peut lire que [des tampons de mémoire pré-allouée sont utilisés](https://github.com/vlang/v/blob/master/doc/docs.md#memory-management). C’est sans doute une optimisation bienvenue, mais qui ne conviendrait peut-être pas à tout le monde. #### Autres aspects sécurisants * Effectue des vérifications lors de l’accès au tableau via index et quitte le programme via un `panic` et un message d’erreur approprié, ce qui évitera de pouvoir accéder à des adresses mémoires hors de la mémoire du programme. * Une abstraction du code multi thread via les « goroutine » et les channels rendant l’implémentation de ce genre de code à la fois simple et sécurisée. * Les variables immuables par défaut et l’absence d’état global évite aussi certains écueils. * Afin d’éviter les erreurs dues à la parallélisation (« race condition »), un mécanisme de mutex, intégré à la syntaxe du langage (`rlock`) mais c’est encore expérimental. Il me semble que le compilateur de Rust propose une analyse bien plus poussée et une sécurité accrue. ### N’y a-t-il pas trop de « Buzz » autour du langage ? Il y a beaucoup d’attention autour du langage. Avec plus de 34 000 étoiles sur GitHub on peut dire que c’est un projet en vogue ! C’est sans doute beaucoup compte tenu de son niveau de maturité ! Aussi, certains critiquent le fait que V n’est pas à la hauteur de ce qu’il annonce. Je trouve que c’est en partie vrai. Un article intéressant [liste tous les aspects « non conformes »](https://mawfig.github.io/2022/06/18/v-lang-in-2022.html) à ce qui est affiché. L’auteur fait parfois preuve d’un peu de mauvaise foi, mais certaines critiques sont justifiées et constructives. Dommage que l’[article ait été aussi mal reçu en interne](https://github.com/vlang/v/discussions/15614)… En vrac : - les fonctions sont annoncées comme « pures » par défaut, mais seulement pour dire que les paramètres sont immuables, ce n’est pas la définition de « pure » en programmation fonctionnelle, - plusieurs éléments sont mis en avant comme la rapidité de compilation (qui est forcément relative à la complexité du code) et le caractère « _safe_ » du langage, - il est mentionné que la recharge du code « à chaud » (_Hot code reloading_) est possible, mais cela reste limité aux fonctions ayant un attribut spécifique. Ce n’est donc pas de la recharge à chaud « générique », comme on peut la trouver dans des langages comme Dart, - V ui, la bibliothèque graphique mise en avant est tout de même très minimaliste et -- pour l’instant -- peu avancée, - dans certains aspects, la documentation ne reflète pas l’état actuel du langage, mais ce à quoi il doit ressembler. Ça manque un peu de rigueur… J’ai l’impression que le créateur du langage a lancé plein de pistes et a posé une intention et quelques bouts de code par-ci, par-là, mais rien de forcément fini. Quand on voit qu’il code en même temps : un langage, un [éditeur de texte](https://github.com/vlang/ved), un [client natif pour les plateformes de discussion instantanées](https://volt-app.com/), [une bibliothèque graphique](https://github.com/vlang/ui), un framework web, un ORM, [un gestionnaire projet](https://github.com/vlang/gitly)… et souhaite même s’attaquer à un [navigateur web](https://github.com/vlang/vbrowser) ! N’importe qui de sérieux se dit que chacun de ces projets nécessite à minima une personne à plein temps dessus. Pour contrer ce portrait un peu négatif, ce qui est chouette, c’est de voir l’enthousiasme généré autour du langage et que rapidement beaucoup de personnes se sont mises à contribuer. Il n’y a qu’à voir la liste des _commits_ pour se rendre compte que V n’est plus développé par une seule personne et ça, c’est rassurant pour l’avenir du langage. Disons qu’Alex a pris à cœur l’adage du libre « _release soon, release often_ », le tout dans une bonne ambiance de « [bazar](https://fr.wikipedia.org/wiki/La_Cath%C3%A9drale_et_le_Bazar)" ». La rigueur pourra venir après :). Personnellement, j’ai découvert plusieurs bugs dans le langage et ai pu contribuer facilement à leur correction. Soit en proposant du code quand cela me paraissait facile (j’ai, par exemple, réécrit la fonction `split` des `string`) ou en fournissant du code de test pour aider à la correction de bogue. Dans les deux cas, je me suis senti très bien accueilli et les contributions furent faciles. Cette [discussion reddit](https://www.reddit.com/r/vlang/comments/kyrs9h/how_does_v_compare_to_zig/gjif6f0?utm_source=share&utm_medium=web2x&context=3) à propos des critiques sur V est intéressante. ## Conclusion V est un petit langage très sympathique. Extrêmement proche de Go, il s’en distingue néanmoins par des emprunts très bien choisis à d’autres langages (comme Rust) et d’autres paradigmes (notamment la programmation fonctionnelle). Le fait qu’il ne nécessite pas de « _runtime_ », ni de ramasse-miette (bon, c’est moins vrai aujourd’hui), le rendent sans doute plus intéressant quand les performances ou la taille des binaires est un enjeu. Le tout fait un langage très moderne tout en étant incroyablement facile à apprendre et utiliser au quotidien. Même s’il est encore jeune et avec un écosystème peu mature, il est rapidement devenu un langage que j’affectionne particulièrement. Il n’est bien entendu pas encore mature pour du code utilisé en production, mais vaut vraiment le détour. De plus, V est un langage accessible (_hackable_) pour celui qui veut apprendre comment fonctionne un compilateur et expérimenter dans ce domaine.