Types

Primitive types

  1. bool
  2. string
  3. i8 i16 int i64 i128 (soon)
  4. byte u16 u32 u64 u128 (soon)
  5. rune // represents a Unicode code point
  6. f32 f64
  7. any_int, any_float // internal intermediate types of number literals
  8. byteptr, voidptr, charptr, size_t // these are mostly used for C interoperability
  9. any // similar to C's void* and Go's interface{}

Please note that unlike C and Go, int is always a 32 bit integer.

There is an exception to the rule that all operators in V must have values of the same type on both sides. A small primitive type on one side can be automatically promoted if it fits completely into the data range of the type on the other side. These are the allowed possibilities:

  1. i8 i16 int i64
  2. f32 f64
  3. byte u16 u32 u64
  4. ptr
  5. i8 i16 int i64

An int value for example can be automatically promoted to f64 or i64 but not to f32 or u32. (f32 would mean precision loss for large values and u32 would mean loss of the sign for negative values).

Strings

  1. name := 'Bob'
  2. println(name.len)
  3. println(name[0]) // indexing gives a byte B
  4. println(name[1..3]) // slicing gives a string 'ob'
  5. windows_newline := '\r\n' // escape special characters like in C
  6. assert windows_newline.len == 2

In V, a string is a read-only array of bytes. String data is encoded using UTF-8. String values are immutable. You cannot mutate elements:

  1. mut s := 'hello 🌎'
  2. s[0] = `H` // not allowed

error: cannot assign to s[i] since V strings are immutable

Note that indexing a string will produce a byte, not a rune. Indexes correspond to bytes in the string, not Unicode code points.

Character literals have type rune. To denote them, use `

  1. rocket := `🚀`
  2. assert 'aloha!'[0] == `a`

Both single and double quotes can be used to denote strings. For consistency, vfmt converts double quotes to single quotes unless the string contains a single quote character.

For raw strings, prepend r. Raw strings are not escaped:

  1. s := r'hello\nworld'
  2. println(s) // "hello\nworld"

String interpolation

Basic interpolation syntax is pretty simple - use $ before a variable name. The variable will be converted to a string and embedded into the literal:

  1. name := 'Bob'
  2. println('Hello, $name!') // Hello, Bob!

It also works with fields: 'age = $user.age'. If you need more complex expressions, use ${}: 'can register = ${user.age > 13}'.

Format specifiers similar to those in C’s printf() are also supported. f, g, x, etc. are optional and specify the output format. The compiler takes care of the storage size, so there is no hd or llu.

  1. x := 123.4567
  2. println('x = ${x:4.2f}')
  3. println('[${x:10}]') // pad with spaces on the left
  4. println('[${int(x):-10}]') // pad with spaces on the right

String operators

  1. name := 'Bob'
  2. bobby := name + 'by' // + is used to concatenate strings
  3. println(bobby) // "Bobby"
  4. mut s := 'hello '
  5. s += 'world' // `+=` is used to append to a string
  6. println(s) // "hello world"

All operators in V must have values of the same type on both sides. You cannot concatenate an integer to a string:

  1. age := 10
  2. println('age = ' + age) // not allowed

error: infix expr: cannot use int (right expression) as string

We have to either convert age to a string:

  1. age := 11
  2. println('age = ' + age.str())

or use string interpolation (preferred):

  1. age := 12
  2. println('age = $age')

Numbers

  1. a := 123

This will assign the value of 123 to a. By default a will have the type int.

You can also use hexadecimal, binary or octal notation for integer literals:

  1. a := 0x7B
  2. b := 0b01111011
  3. c := 0o173

All of these will be assigned the same value, 123. They will all have type int, no matter what notation you used.

V also supports writing numbers with _ as separator:

  1. num := 1_000_000 // same as 1000000
  2. three := 0b0_11 // same as 0b11
  3. float_num := 3_122.55 // same as 3122.55
  4. hexa := 0xF_F // same as 255
  5. oct := 0o17_3 // same as 0o173

If you want a different type of integer, you can use casting:

  1. a := i64(123)
  2. b := byte(42)
  3. c := i16(12345)

Assigning floating point numbers works the same way:

  1. f := 1.0
  2. f1 := f64(3.14)
  3. f2 := f32(3.14)

If you do not specify the type explicitly, by default float literals will have the type of f64.

Arrays

  1. mut nums := [1, 2, 3]
  2. println(nums) // "[1, 2, 3]"
  3. println(nums[1]) // "2"
  4. nums[1] = 5
  5. println(nums) // "[1, 5, 3]"
  6. println(nums[0..2]) // slicing gives an array "[1, 5]"
  7. println(nums.len) // "3"
  8. nums = [] // The array is now empty
  9. println(nums.len) // "0"
  10. // Declare an empty array:
  11. users := []int{}

The type of an array is determined by the first element:

  • [1, 2, 3] is an array of ints ([]int).
  • ['a', 'b'] is an array of strings ([]string).

The user can explicitly specify the type for the first element: [byte(16), 32, 64, 128]. V arrays are homogeneous (all elements must have the same type). This means that code like [1, 'a'] will not compile.

The .len field returns the length of the array. Note that it’s a read-only field, and it can’t be modified by the user. Exported fields are read-only by default in V. See Access modifiers.

Array operations

  1. mut nums := [1, 2, 3]
  2. nums << 4
  3. println(nums) // "[1, 2, 3, 4]"
  4. // append array
  5. nums << [5, 6, 7]
  6. println(nums) // "[1, 2, 3, 4, 5, 6, 7]"
  7. mut names := ['John']
  8. names << 'Peter'
  9. names << 'Sam'
  10. // names << 10 <-- This will not compile. `names` is an array of strings.
  11. println(names.len) // "3"
  12. println('Alex' in names) // "false"

<< is an operator that appends a value to the end of the array. It can also append an entire array.

val in array returns true if the array contains val. See in operator.

Initializing array properties

During initialization you can specify the capacity of the array (cap), its initial length (len), and the default element (init):

  1. arr := []int{len: 5, init: -1}
  2. // `[-1, -1, -1, -1, -1]`

Setting the capacity improves performance of insertions, as it reduces the number of reallocations needed:

  1. mut numbers := []int{ cap: 1000 }
  2. println(numbers.len) // 0
  3. // Now appending elements won't reallocate
  4. for i in 0 .. 1000 {
  5. numbers << i
  6. }

Note: The above code uses a range for statement.

Array methods

All arrays can be easily printed with println(arr) and converted to a string with s := arr.str().

Copying the data from the array is done with .clone():

  1. nums := [1, 2, 3]
  2. nums_copy := nums.clone()

Arrays can be efficiently filtered and mapped with the .filter() and .map() methods:

  1. nums := [1, 2, 3, 4, 5, 6]
  2. even := nums.filter(it % 2 == 0)
  3. println(even) // [2, 4, 6]
  4. // filter can accept anonymous functions
  5. even_fn := nums.filter(fn (x int) bool {
  6. return x % 2 == 0
  7. })
  8. println(even_fn)
  9. words := ['hello', 'world']
  10. upper := words.map(it.to_upper())
  11. println(upper) // ['HELLO', 'WORLD']
  12. // map can also accept anonymous functions
  13. upper_fn := words.map(fn (w string) string {
  14. return w.to_upper()
  15. })
  16. println(upper_fn) // ['HELLO', 'WORLD']

it is a builtin variable which refers to element currently being processed in filter/map methods.

Multidimensional Arrays

Arrays can have more than one dimension.

2d array example:

  1. mut a := [][]int{len:2, init: []int{len:3}}
  2. a[0][1] = 2
  3. println(a) // [[0, 2, 0], [0, 0, 0]]

3d array example:

  1. mut a := [][][]int{len:2, init: [][]int{len:3, init: []int{len:2}}}
  2. a[0][1][1] = 2
  3. println(a) // [[[0, 0], [0, 2], [0, 0]], [[0, 0], [0, 0], [0, 0]]]

Sorting arrays

Sorting arrays of all kinds is very simple and intuitive. Special variables a and b are used when providing a custom sorting condition.

  1. mut numbers := [1, 3, 2]
  2. numbers.sort() // 1, 2, 3
  3. numbers.sort(a > b) // 3, 2, 1
  1. struct User { age int name string }
  2. mut users := [User{21, 'Bob'}, User{20, 'Zarkon'}, User{25, 'Alice'}]
  3. users.sort(a.age < b.age) // sort by User.age int field
  4. users.sort(a.name > b.name) // reverse sort by User.name string field

Maps

  1. mut m := map[string]int // Only maps with string keys are allowed for now
  2. m['one'] = 1
  3. m['two'] = 2
  4. println(m['one']) // "1"
  5. println(m['bad_key']) // "0"
  6. println('bad_key' in m) // Use `in` to detect whether such key exists
  7. m.delete('two')
  8. // Short syntax
  9. numbers := {
  10. 'one': 1
  11. 'two': 2
  12. }