Statements & expressions
If
a := 10
b := 20
if a < b {
println('$a < $b')
} else if a > b {
println('$a > $b')
} else {
println('$a == $b')
}
if
statements are pretty straightforward and similar to most other languages.
Unlike other C-like languages,
there are no parentheses surrounding the condition and the braces are always required.
if
can be used as an expression:
num := 777
s := if num % 2 == 0 { 'even' } else { 'odd' }
println(s)
// "odd"
Type checks and casts
You can check the current type of a sum type using is
and its negated form !is
.
You can do it either in an if
:
// cgen
struct Abc {
val string
}
struct Xyz {
foo string
}
type Alphabet = Abc | Xyz
x := Alphabet(Abc{'test'}) // sum type
if x is Abc {
// x is automatically casted to Abc and can be used here
println(x)
}
if x !is Abc {
println('Not Abc')
}
or using match
:
// oksyntax
match x {
Abc {
// x is automatically casted to Abc and can be used here
println(x)
}
Xyz {
// x is automatically casted to Xyz and can be used here
println(x)
}
}
This works also with struct fields:
struct MyStruct {
x int
}
struct MyStruct2 {
y string
}
type MySumType = MyStruct | MyStruct2
struct Abc {
bar MySumType
}
x := Abc{
bar: MyStruct{123} // MyStruct will be converted to MySumType type automatically
}
if x.bar is MyStruct {
// x.bar is automatically casted
println(x.bar)
}
else if x.bar is MyStruct2 as new_var {
// you can use `as` to create aliases
println(new_var)
}
match x.bar {
MyStruct {
// x.bar is automatically casted
println(x.bar)
}
else {}
}
Mutable variables can change, and doing a cast would be unsafe.
However, sometimes it’s useful to type cast despite mutability.
In such cases the developer must mark the expression with the mut
keyword
to tell the compiler that they know what they’re doing.
It works like this:
// oksyntax
mut x := MySumType(MyStruct{123})
if mut x is MyStruct {
// x is casted to MyStruct even if it's mutable
// without the mut keyword that wouldn't work
println(x)
}
// same with match
match mut x {
MyStruct {
// x is casted to MyStruct even if it's mutable
// without the mut keyword that wouldn't work
println(x)
}
}
In operator
in
allows to check whether an array or a map contains an element.
To do the opposite, use !in
.
nums := [1, 2, 3]
println(1 in nums) // true
println(4 !in nums) // true
m := {
'one': 1
'two': 2
}
println('one' in m) // true
println('three' !in m) // true
It’s also useful for writing boolean expressions that are clearer and more compact:
enum Token {
plus
minus
div
mult
}
struct Parser {
token Token
}
parser := Parser{}
if parser.token == .plus || parser.token == .minus || parser.token == .div || parser.token == .mult {
// ...
}
if parser.token in [.plus, .minus, .div, .mult] {
// ...
}
V optimizes such expressions,
so both if
statements above produce the same machine code and no arrays are created.
For loop
V has only one looping keyword: for
, with several forms.
for
/in
This is the most common form. You can use it with an array, map or numeric range.
Array for
numbers := [1, 2, 3, 4, 5]
for num in numbers {
println(num)
}
names := ['Sam', 'Peter']
for i, name in names {
println('$i) $name')
// Output: 0) Sam
// 1) Peter
}
The for value in arr
form is used for going through elements of an array.
If an index is required, an alternative form for index, value in arr
can be used.
Note, that the value is read-only. If you need to modify the array while looping, you need to declare the element as mutable:
mut numbers := [0, 1, 2]
for mut num in numbers {
num++
}
println(numbers) // [1, 2, 3]
When an identifier is just a single underscore, it is ignored.
Custom iterators
Types that implement a next
method returning an Option
can be iterated
with a for
loop.
struct SquareIterator {
arr []int
mut:
idx int
}
fn (mut iter SquareIterator) next() ?int {
if iter.idx >= iter.arr.len {
return error('')
}
defer {
iter.idx++
}
return iter.arr[iter.idx] * iter.arr[iter.idx]
}
nums := [1, 2, 3, 4, 5]
iter := SquareIterator{
arr: nums
}
for squared in iter {
println(squared)
}
The code above prints:
1
4
9
16
25
Map for
m := {
'one': 1
'two': 2
}
for key, value in m {
println('$key -> $value')
// Output: one -> 1
// two -> 2
}
Either key or value can be ignored by using a single underscore as the identifier.
m := {
'one': 1
'two': 2
}
// iterate over keys
for key, _ in m {
println(key)
// Output: one
// two
}
// iterate over values
for _, value in m {
println(value)
// Output: 1
// 2
}
Range for
// Prints '01234'
for i in 0 .. 5 {
print(i)
}
low..high
means an exclusive range, which represents all values
from low
up to but not including high
.
Condition for
mut sum := 0
mut i := 0
for i <= 100 {
sum += i
i++
}
println(sum) // "5050"
This form of the loop is similar to while
loops in other languages.
The loop will stop iterating once the boolean condition evaluates to false.
Again, there are no parentheses surrounding the condition, and the braces are always required.
Bare for
mut num := 0
for {
num += 2
if num >= 10 {
break
}
}
println(num) // "10"
The condition can be omitted, resulting in an infinite loop.
C for
for i := 0; i < 10; i += 2 {
// Don't print 6
if i == 6 {
continue
}
println(i)
}
Finally, there’s the traditional C style for
loop. It’s safer than the while
form
because with the latter it’s easy to forget to update the counter and get
stuck in an infinite loop.
Here i
doesn’t need to be declared with mut
since it’s always going to be mutable by definition.
Labelled break & continue
break
and continue
control the innermost for
loop by default.
You can also use break
and continue
followed by a label name to refer to an outer for
loop:
outer: for i := 4; true; i++ {
println(i)
for {
if i < 7 {
continue outer
} else {
break outer
}
}
}
The label must immediately precede the outer loop. The above code prints:
4
5
6
7
Match
os := 'windows'
print('V is running on ')
match os {
'darwin' { println('macOS.') }
'linux' { println('Linux.') }
else { println(os) }
}
A match statement is a shorter way to write a sequence of if - else
statements.
When a matching branch is found, the following statement block will be run.
The else branch will be run when no other branches match.
number := 2
s := match number {
1 { 'one' }
2 { 'two' }
else { 'many' }
}
A match statement can also to be used as an if - else if - else
alternative:
match true {
2 > 4 { println('if') }
3 == 4 { println('else if') }
2 == 2 { println('else if2') }
else { println('else') }
}
// 'else if2' should be printed
or as an unless
alternative: unless Ruby
match false {
2 > 4 { println('if') }
3 == 4 { println('else if') }
2 == 2 { println('else if2') }
else { println('else') }
}
// 'if' should be printed
A match expression returns the value of the final expression from the matching branch.
enum Color {
red
blue
green
}
fn is_red_or_blue(c Color) bool {
return match c {
.red, .blue { true } // comma can be used to test multiple values
.green { false }
}
}
A match statement can also be used to branch on the variants of an enum
by using the shorthand .variant_here
syntax. An else
branch is not allowed
when all the branches are exhaustive.
c := `v`
typ := match c {
`0`...`9` { 'digit' }
`A`...`Z` { 'uppercase' }
`a`...`z` { 'lowercase' }
else { 'other' }
}
println(typ)
// 'lowercase'
You can also use ranges as match
patterns. If the value falls within the range
of a branch, that branch will be executed.
Note that the ranges use ...
(three dots) rather than ..
(two dots). This is
because the range is inclusive of the last element, rather than exclusive
(as ..
ranges are). Using ..
in a match branch will throw an error.
Note: match
as an expression is not usable in for
loop and if
statements.
Defer
A defer statement defers the execution of a block of statements until the surrounding function returns.
import os
fn read_log() {
mut ok := false
mut f := os.open('log.txt') or { panic(err) }
defer {
f.close()
}
// ...
if !ok {
// defer statement will be called here, the file will be closed
return
}
// ...
// defer statement will be called here, the file will be closed
}
If the function returns a value the defer
block is executed after the return
expression is evaluated:
import os
enum State {
normal
write_log
return_error
}
// write log file and return number of bytes written
fn write_log(s State) ?int {
mut f := os.create('log.txt')?
defer {
f.close()
}
if s == .write_log {
// `f.close()` will be called after `f.write()` has been
// executed, but before `write_log()` finally returns the
// number of bytes written to `main()`
return f.writeln('This is a log file')
} else if s == .return_error {
// the file will be closed after the `error()` function
// has returned - so the error message will still report
// it as open
return error('nothing written; file open: $f.is_opened')
}
// the file will be closed here, too
return 0
}
fn main() {
n := write_log(.return_error) or {
println('Error: $err')
0
}
println('$n bytes written')
}