if can be used as an expression:

    1. num := 777
    2. s := if num % 2 == 0 { 'even' } else { 'odd' }
    3. println(s)
    4. // "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:

    1. // cgen
    2. struct Abc {
    3. val string
    4. }
    5. struct Xyz {
    6. foo string
    7. }
    8. type Alphabet = Abc | Xyz
    9. x := Alphabet(Abc{'test'}) // sum type
    10. if x is Abc {
    11. // x is automatically casted to Abc and can be used here
    12. println(x)
    13. }
    14. if x !is Abc {
    15. println('Not Abc')
    16. }

    or using match:

    1. // oksyntax
    2. match x {
    3. Abc {
    4. // x is automatically casted to Abc and can be used here
    5. println(x)
    6. }
    7. Xyz {
    8. // x is automatically casted to Xyz and can be used here
    9. println(x)
    10. }
    11. }

    This works also with struct fields:

    1. struct MyStruct {
    2. x int
    3. }
    4. struct MyStruct2 {
    5. y string
    6. }
    7. type MySumType = MyStruct | MyStruct2
    8. struct Abc {
    9. bar MySumType
    10. }
    11. x := Abc{
    12. bar: MyStruct{123} // MyStruct will be converted to MySumType type automatically
    13. }
    14. if x.bar is MyStruct {
    15. // x.bar is automatically casted
    16. println(x.bar)
    17. }
    18. else if x.bar is MyStruct2 as new_var {
    19. // you can use `as` to create aliases
    20. println(new_var)
    21. }
    22. match x.bar {
    23. MyStruct {
    24. // x.bar is automatically casted
    25. println(x.bar)
    26. }
    27. else {}
    28. }

    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:

    1. // oksyntax
    2. mut x := MySumType(MyStruct{123})
    3. if mut x is MyStruct {
    4. // x is casted to MyStruct even if it's mutable
    5. // without the mut keyword that wouldn't work
    6. println(x)
    7. }
    8. // same with match
    9. match mut x {
    10. MyStruct {
    11. // x is casted to MyStruct even if it's mutable
    12. println(x)
    13. }
    14. }

    in allows to check whether an array or a map contains an element. To do the opposite, use !in.

    1. nums := [1, 2, 3]
    2. println(1 in nums) // true
    3. println(4 !in nums) // true
    4. m := {
    5. 'one': 1
    6. 'two': 2
    7. }
    8. println('one' in m) // true
    9. println('three' !in m) // true

    It’s also useful for writing boolean expressions that are clearer and more compact:

    1. enum Token {
    2. minus
    3. div
    4. mult
    5. }
    6. struct Parser {
    7. token Token
    8. }
    9. parser := Parser{}
    10. if parser.token == .plus || parser.token == .minus || parser.token == .div || parser.token == .mult {
    11. // ...
    12. }
    13. if parser.token in [.plus, .minus, .div, .mult] {
    14. // ...
    15. }

    V optimizes such expressions, so both if statements above produce the same machine code and no arrays are created.

    V has only one looping keyword: for, with several forms.

    for/in

    Array for
    1. numbers := [1, 2, 3, 4, 5]
    2. for num in numbers {
    3. println(num)
    4. }
    5. names := ['Sam', 'Peter']
    6. for i, name in names {
    7. println('$i) $name')
    8. // Output: 0) Sam
    9. // 1) Peter
    10. }

    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:

    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.

    1. struct SquareIterator {
    2. arr []int
    3. mut:
    4. idx int
    5. }
    6. fn (mut iter SquareIterator) next() ?int {
    7. if iter.idx >= iter.arr.len {
    8. return error('')
    9. }
    10. defer {
    11. iter.idx++
    12. }
    13. return iter.arr[iter.idx] * iter.arr[iter.idx]
    14. }
    15. nums := [1, 2, 3, 4, 5]
    16. iter := SquareIterator{
    17. arr: nums
    18. }
    19. for squared in iter {
    20. println(squared)
    21. }

    The code above prints:

    1. 1
    2. 4
    3. 9
    4. 16
    5. 25
    Map for
    1. m := {
    2. 'one': 1
    3. 'two': 2
    4. }
    5. for key, value in m {
    6. println('$key -> $value')
    7. // Output: one -> 1
    8. // two -> 2
    9. }

    Either key or value can be ignored by using a single underscore as the identifier.

    1. m := {
    2. 'one': 1
    3. 'two': 2
    4. }
    5. // iterate over keys
    6. for key, _ in m {
    7. println(key)
    8. // Output: one
    9. // two
    10. }
    11. // iterate over values
    12. for _, value in m {
    13. println(value)
    14. // Output: 1
    15. // 2
    Range for
    1. // Prints '01234'
    2. for i in 0 .. 5 {
    3. print(i)
    4. }

    low..high means an exclusive range, which represents all values from low up to but not including high.

    Condition for

    1. mut sum := 0
    2. mut i := 0
    3. for i <= 100 {
    4. sum += i
    5. i++
    6. }
    7. 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

    1. mut num := 0
    2. for {
    3. num += 2
    4. if num >= 10 {
    5. break
    6. }
    7. }
    8. println(num) // "10"

    The condition can be omitted, resulting in an infinite loop.

    C for

    1. for i := 0; i < 10; i += 2 {
    2. // Don't print 6
    3. if i == 6 {
    4. continue
    5. }
    6. println(i)
    7. }

    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

    The label must immediately precede the outer loop. The above code prints:

    1. 4
    2. 5
    3. 6
    4. 7
    1. os := 'windows'
    2. print('V is running on ')
    3. match os {
    4. 'darwin' { println('macOS.') }
    5. 'linux' { println('Linux.') }
    6. else { println(os) }
    7. }

    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.

    1. number := 2
    2. s := match number {
    3. 1 { 'one' }
    4. 2 { 'two' }
    5. else { 'many' }
    6. }

    A match statement can also to be used as an if - else if - else alternative:

    1. match true {
    2. 2 > 4 { println('if') }
    3. 3 == 4 { println('else if') }
    4. 2 == 2 { println('else if2') }
    5. else { println('else') }
    6. }
    7. // 'else if2' should be printed

    or as an unless alternative:

    1. match false {
    2. 2 > 4 { println('if') }
    3. 3 == 4 { println('else if') }
    4. 2 == 2 { println('else if2') }
    5. else { println('else') }
    6. }
    7. // 'if' should be printed

    A match expression returns the value of the final expression from the matching branch.

    1. enum Color {
    2. red
    3. blue
    4. green
    5. }
    6. fn is_red_or_blue(c Color) bool {
    7. return match c {
    8. .red, .blue { true } // comma can be used to test multiple values
    9. .green { false }
    10. }
    11. }

    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.

    1. c := `v`
    2. typ := match c {
    3. `0`...`9` { 'digit' }
    4. `A`...`Z` { 'uppercase' }
    5. `a`...`z` { 'lowercase' }
    6. else { 'other' }
    7. }
    8. println(typ)
    9. // '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.

    A defer statement defers the execution of a block of statements until the surrounding function returns.

    1. import os
    2. fn read_log() {
    3. mut ok := false
    4. mut f := os.open('log.txt') or { panic(err) }
    5. defer {
    6. f.close()
    7. }
    8. // ...
    9. if !ok {
    10. // defer statement will be called here, the file will be closed
    11. return
    12. }
    13. // ...

    If the function returns a value the defer block is executed after the return expression is evaluated: