2. Types, variables immuables, structs et méthodes
Sections
Réglages de lecture
Aucune voix pour cette langue dans ce navigateur.
Plus de voix
Code et termes techniques en anglais
Désactivé : la voix de la page lit tout, sans pause entre deux voix.
Aucune voix pour cette langue dans ce navigateur.
Plus de voix
D’autres voix : Microsoft Edge propose des voix naturelles en ligne, et Windows (Paramètres › Heure et langue › Voix), macOS et Android en installent d’autres.
Code : examples/l02_variables.v et examples/l02_structs.v — v run examples/l02_structs.v depuis code/v-for-csharp-java.
Variables
Section intitulée « Variables »course := 'V for C#/Java developers'lessons := 4mut done := 0done = 2println('${course}: ${done}/${lessons} lessons')V for C#/Java developers: 2/4 lessons| C# | Java | V | |
|---|---|---|---|
| Déclarer, type inféré | var lessons = 4; |
var lessons = 4; |
lessons := 4 |
| Déclarer, type donné | long big = 3_000_000_000; |
long big = 3_000_000_000L; |
big := i64(3_000_000_000) |
| Non réassignable | const, champ readonly |
final var |
le comportement par défaut |
| Réassignable | le comportement par défaut | le comportement par défaut | mut done := 0 |
| Affecter | done = 2; |
done = 2; |
done = 2 |
:= est la seule façon de déclarer une variable, donc une variable a toujours une valeur. = ne fait qu’affecter, et seulement à une variable mut :
fn main() { done := 0 done = 2 println(done)}e02_immutable.v:3:2: error: `done` is immutable, declare it with `mut` to make it mutable 1 | fn main() { 2 | done := 0 3 | done = 2 | ~~~~ 4 | println(done) 5 | }Une variable ne peut pas reprendre le nom d’une variable d’un bloc englobant. C# (CS0136) et Java le rejettent aussi :
fn main() { total := 10 if total > 5 { total := 20 println(total) }}e02_shadowing.v:4:3: error: redefinition of `total` 2 | total := 10 3 | if total > 5 { 4 | total := 20 | ~~~~~ 5 | println(total) 6 | }Une variable inutilisée est un avertissement, comme le CS0219 de C#, là où javac ne dit rien :
w02_unused.v:2:2: warning: unused variable: `count` 1 | fn main() { 2 | count := 3 | ~~~~~ 3 | println('done') 4 | }doneLe même fichier avec v -prod ne compile pas : les avertissements deviennent des erreurs dans les builds de production.
e02_unused_prod.v:3:2: error: unused variable: `count` 1 | // flags: -prod 2 | fn main() { 3 | count := 3 | ~~~~~ 4 | println('done') 5 | }Types primitifs
Section intitulée « Types primitifs »| V | C# | Java | Notes |
|---|---|---|---|
bool |
bool |
boolean |
|
i8, i16, int, i64 |
sbyte, short, int, long |
byte, short, int, long |
int fait toujours 32 bits |
u8, u16, u32, u64 |
byte, ushort, uint, ulong |
— | Java n’a pas de types non signés |
f32, f64 |
float, double |
float, double |
|
isize, usize |
nint, nuint |
— | la taille d’un pointeur |
rune |
int contenant un point de code, Rune |
int contenant un point de code |
un point de code Unicode ; char en C# et en Java est une unité UTF-16 |
string |
string (UTF-16) |
String (UTF-16) |
UTF-8, immuable |
Source : types de V. Un littéral entier est un int, un littéral flottant un f64, et une conversion s’écrit comme un appel : i64(3_000_000_000).
big := i64(3_000_000_000)println('${typeof(lessons).name} ${typeof(big).name}')total := big + lessonsprintln('${typeof(total).name} ${total}')int i64i64 3000000004V promeut un type plus petit vers un type plus grand quand aucune valeur ne peut être perdue : int vers i64 ou f64, u8 vers int. Il ne mélange jamais signé et non signé, là où C# convertirait silencieusement uint + int en long :
fn main() { offset := -1 size := u32(5) println(size + offset)}e02_mixed_signs.v:4:10: error: mismatched types `u32` and `int` 2 | offset := -1 3 | size := u32(5) 4 | println(size + offset) | ~~~~~~~~~~~~~ 5 | }e02_mixed_signs.v:4:2: error: `println` can not print void expressions 2 | offset := -1 3 | size := u32(5) 4 | println(size + offset) | ~~~~~~~~~~~~~~~~~~~~~~ 5 | }La seconde erreur est une conséquence de la première : une fois que l’addition n’a pas de type, println n’a rien à afficher. Lis d’abord la première erreur.
V ne convertit pas non plus un nombre en chaîne. "lessons: " + 4 est valide en C# et en Java ; en V, utilise l’interpolation :
fn main() { lessons := 4 println('lessons: ' + lessons)}e02_string_plus_int.v:3:10: error: infix expr: cannot use `int` (right expression) as `string` 1 | fn main() { 2 | lessons := 4 3 | println('lessons: ' + lessons) | ~~~~~~~~~~~~~~~~~~~~~ 4 | }L’arithmétique se comporte comme en C# dans un contexte unchecked et comme en Java : les entiers reviennent au début de leur plage en cas de dépassement, et la division entière tronque vers zéro.
max := 2147483647println(max + 1)println(i8(127) + 1)println('${7 / 2} ${-7 / 2} ${-7 % 2}')-2147483648-1283 -3 -1Les chaînes sont des octets UTF-8
Section intitulée « Les chaînes sont des octets UTF-8 »word := 'journée'println('${word.len} bytes, ${word.runes().len} runes')println('${word[0]} ${typeof(word[0]).name} ${`é`} ${typeof(`é`).name}')8 bytes, 7 runes106 u8 é runeEn C# et en Java, "journée".Length et "journée".length() valent 7 : ils comptent des unités UTF-16. En V, len compte les octets de l’encodage UTF-8, et é en prend deux. Indexer une chaîne donne un octet (u8), pas un caractère : word[0] vaut 106, le code de j. runes() décode les points de code ; un littéral de rune utilise des accents graves.
if et match sont des expressions
Section intitulée « if et match sont des expressions »V n’a pas d’opérateur ternaire : if renvoie une valeur. match est l’équivalent d’une expression switch en C# ou d’un switch avec flèches en Java, et doit couvrir tous les cas, ici avec else :
status := if done == lessons { 'finished' } else { 'in progress' }label := match done { 0 { 'not started' } 1, 2, 3 { 'started' } else { 'done' }}in progressstartedDes structs au lieu de classes
Section intitulée « Des structs au lieu de classes »V n’a ni classes, ni héritage, ni constructeurs. Une struct contient les données, et les méthodes sont déclarées à côté :
struct Page { url string locale string = 'en' title string lines int}mission := Page{ url: '/v-for-csharp-java/' title: 'Mission' lines: 80}lesson := Page{'/v-for-csharp-java/02-types-structs-methods/', 'en', '2. Types, structs and methods', 300}- Un littéral de struct nomme les champs, ou les donne tous dans l’ordre.
- Un champ qui n’est pas donné prend sa valeur par défaut :
localevaut'en', et sinon la valeur zéro (0,'', un tableau vide). - Il n’y a pas de
null: un champstringn’est jamais absent, seulement vide.
@[required] rend un champ obligatoire dans chaque littéral, comme le modificateur required de C# 11 :
struct Page { url string @[required] title string}
fn main() { p := Page{ title: 'Mission' } println(p)}e02_required_field.v:7:7: error: field `Page.url` must be initialized 5 | 6 | fn main() { 7 | p := Page{ | ~~~~~ 8 | title: 'Mission' 9 | }Les champs sont privés et immuables par défaut
Section intitulée « Les champs sont privés et immuables par défaut »struct Page { title string lines int}
fn main() { mut p := Page{'Mission', 80} p.lines = 81 println(p)}e02_immutable_field.v:8:4: error: field `lines` of struct `Page` is immutable 6 | fn main() { 7 | mut p := Page{'Mission', 80} 8 | p.lines = 81 | ~~~~~ 9 | println(p) 10 | }mut p rend la variable mutable, pas ses champs. Les modificateurs d’accès sont des sections de la struct :
| Section V | Lecture depuis un autre module | Écriture dans le module | Écriture depuis un autre module | Équivalent C# le plus proche | Équivalent Java le plus proche |
|---|---|---|---|---|---|
| (aucune) | non | non | non | private readonly |
private final |
mut: |
non | oui | non | private |
private |
pub: |
oui | non | non | public + init |
public final |
pub mut: |
oui | oui | non | public + private set |
getter public, setter privé |
__global: |
oui | oui | oui | public |
public |
Le code du cours utilise un seul module, il n’a donc besoin que de mut: :
struct Progress {mut: done intpub: total int}Méthodes
Section intitulée « Méthodes »fn (p Page) is_lesson() bool { return p.title.len > 0 && p.title[0].is_digit()}
fn (mut p Progress) complete() { p.done++}Une méthode est une fonction avec un receveur entre fn et son nom. Il n’y a pas de this : le receveur a un nom, par convention une lettre. Le receveur est immuable sauf s’il est déclaré mut, et une méthode mut ne peut être appelée que sur une variable mutable :
fn main() { p := Progress{} p.complete() println(p.done)}e02_mut_receiver.v:12:2: error: `p` is immutable, declare it with `mut` to make it mutable 10 | fn main() { 11 | p := Progress{} 12 | p.complete() | ^ 13 | println(p.done) 14 | }La même règle s’applique aux paramètres : fn f(mut pages []Page) s’appelle f(mut pages), si bien que le site d’appel montre ce qui peut changer. Mais V refuse mut pour un nombre, là où C# a ref int :
fn double(mut n int) { n *= 2}e02_mut_int_param.v:1:17: error: mutable arguments are only allowed for arrays, interfaces, maps, pointers, structs or their aliasesreturn values instead: `fn foo(mut n int) {` => `fn foo(n int) int {` 1 | fn double(mut n int) { | ~~~ 2 | n *= 2 3 | }str(), méthodes statiques et mise à jour de struct
Section intitulée « str(), méthodes statiques et mise à jour de struct »fn (p Page) str() string { return '${p.title} (${p.locale}, ${p.lines} lines)'}
fn Page.translation(p Page, locale string, title string) Page { return Page{ ...p url: '/${locale}${p.url}' locale: locale title: title }}str()joue le rôle deToString()et detoString():printlnet${…}l’appellent.fn Page.translationest une méthode statique, appeléePage.translation(…). C’est la façon V d’écrire une fabrique, puisqu’il n’y a pas de constructeurs....pcopie les champs depet remplace ceux qui sont listés, comme une expressionwithC# sur un record.
println(mission)println(lesson.is_lesson())french := Page.translation(lesson, 'fr', '2. Types, structs et méthodes')println(french.url)Mission (en, 80 lines)true/fr/v-for-csharp-java/02-types-structs-methods/Arguments nommés et optionnels
Section intitulée « Arguments nommés et optionnels »V n’a ni surcharge, ni arguments par défaut, ni arguments nommés. Deux fonctions du même nom sont rejetées, quels que soient leurs paramètres :
fn describe(lines int) string { return '${lines} lines'}
fn describe(title string) string { return 'title: ${title}'}builder error: redefinition of function `describe`e02_overload.v:1:1: conflicting declaration: fn describe(lines int) string 1 | fn describe(lines int) string { | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 2 | return '${lines} lines' 3 | }e02_overload.v:5:1: conflicting declaration: fn describe(title string) string 3 | } 4 | 5 | fn describe(title string) string { | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 6 | return 'title: ${title}' 7 | }Une struct marquée @[params], en dernier paramètre, offre le même confort que les arguments nommés et optionnels de C#, ou qu’un builder en Java :
@[params]struct ListOptions { only_lessons bool max int = 10}
fn list(pages []Page, opts ListOptions) { // …}
list(pages, max: 2)list(pages, only_lessons: true)list(pages)L’appel liste les champs sans accolades ; ceux qui sont omis gardent leur valeur par défaut.
Les structs sont des valeurs
Section intitulée « Les structs sont des valeurs »copy := lessonprintln(copy == lesson)mut p1 := Progress{ total: 4}mut p2 := p1p2.complete()p1.complete()p1.complete()println('p1 ${p1.done}/${p1.total}, p2 ${p2.done}/${p2.total}')println(p1)truep1 2/4, p2 1/4Progress{ done: 2 total: 4}Une struct V se comporte comme une struct ou un record struct C#, pas comme une classe : l’affectation la copie, et == compare les champs, comme l’égalité d’un record C# ou d’un record Java. mut p2 := p1 crée un second Progress. Une struct sans str() affiche tous ses champs.
En coulisses, V passe un argument struct immuable par valeur ou par référence, comme bon lui semble (références) ; comme l’appelé ne peut pas le modifier, la différence ne se voit pas. La leçon 4 revient sur les références, &Page, et sur ce qui est partagé : les tableaux et les maps ne suivent pas la même règle que les structs.
À retenir
Section intitulée « À retenir »x := 1déclare une variable immuable ;mutest nécessaire pour la réassigner, pour modifier un champ, ou pour appeler une méthodemut.intfait 32 bits partout ; V ne mélange jamais types signés et non signés, et ne convertit jamais implicitement un nombre en chaîne.- Une chaîne est en UTF-8 :
lencompte des octets, l’indexation donne unu8,runes()donne les points de code. - Une struct a des champs privés et immuables par défaut, des méthodes avec un receveur explicite, et pas de constructeur : les méthodes statiques et
...ples remplacent. - Ni surcharge, ni arguments par défaut ou nommés : une struct
@[params]les remplace. - Les structs sont des valeurs, copiées à l’affectation et comparées champ par champ.
Exercices
Section intitulée « Exercices »- Écris une struct
Courseavec un nom, un nombre de leçons et un compteur mutabledone, une méthodefinish_lesson()qui ne dépasse jamais le nombre de leçons, et unstr()qui afficheV: 2/2. Appellefinish_lesson()trois fois sur un cours de deux leçons.
Solution
struct Course { name string lessons intmut: done int}
fn (mut c Course) finish_lesson() { if c.done < c.lessons { c.done++ }}
fn (c Course) str() string { return '${c.name}: ${c.done}/${c.lessons}'}
fn main() { mut v := Course{ name: 'V' lessons: 2 } for _ in 0 .. 3 { v.finish_lesson() } println(v)}V: 2/2for _ in 0 .. 3 répète trois fois ; _ ignore le compteur, et 0 .. 3 exclut 3.
- Qu’affiche ce code ? Une
class CourseC# afficherait-elle la même chose ?
mut a := Course{ name: 'Rust' lessons: 15}mut b := ab.finish_lesson()println('${a} | ${b}')Solution
Rust: 0/15 | Rust: 1/15mut b := a copie la struct : a ne change pas. Avec une classe C#, b et a seraient le même objet et afficheraient tous deux Rust: 1/15 ; avec une struct C#, la sortie serait la même qu’en V.
fn double(mut n int)ne compile pas. Écris la fonction que V attend à la place, et appelle-la.
Solution
Renvoie la nouvelle valeur, comme le suggère le message d’erreur :
fn double(n int) int { return n * 2}
x := double(2)V n’autorise les paramètres mut que pour les tableaux, les maps, les structs, les interfaces et les pointeurs. La documentation préfère même renvoyer des valeurs pour ceux-là, et réserve la modification d’un argument sur place aux parties d’un programme où les copies coûtent trop cher.