Types
Primitive types
bool
string
i8 i16 int i64 i128 (soon)
byte u16 u32 u64 u128 (soon)
rune // represents a Unicode code point
f32 f64
any_int, any_float // internal intermediate types of number literals
byteptr, voidptr, charptr, size_t // these are mostly used for C interoperability
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:
i8 → i16 → int → i64
↘ ↘
f32 → f64
↗ ↗
byte → u16 → u32 → u64 ⬎
↘ ↘ ↘ ptr
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
name := 'Bob'
println(name.len)
println(name[0]) // indexing gives a byte B
println(name[1..3]) // slicing gives a string 'ob'
windows_newline := '\r\n' // escape special characters like in C
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:
mut s := 'hello 🌎'
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 `
rocket := `🚀`
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:
s := r'hello\nworld'
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:
name := 'Bob'
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
.
x := 123.4567
println('x = ${x:4.2f}')
println('[${x:10}]') // pad with spaces on the left
println('[${int(x):-10}]') // pad with spaces on the right
String operators
name := 'Bob'
bobby := name + 'by' // + is used to concatenate strings
println(bobby) // "Bobby"
mut s := 'hello '
s += 'world' // `+=` is used to append to a string
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:
age := 10
println('age = ' + age) // not allowed
error: infix expr: cannot use
int
(right expression) asstring
We have to either convert age
to a string
:
age := 11
println('age = ' + age.str())
or use string interpolation (preferred):
age := 12
println('age = $age')
Numbers
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:
a := 0x7B
b := 0b01111011
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:
num := 1_000_000 // same as 1000000
three := 0b0_11 // same as 0b11
float_num := 3_122.55 // same as 3122.55
hexa := 0xF_F // same as 255
oct := 0o17_3 // same as 0o173
If you want a different type of integer, you can use casting:
a := i64(123)
b := byte(42)
c := i16(12345)
Assigning floating point numbers works the same way:
f := 1.0
f1 := f64(3.14)
f2 := f32(3.14)
If you do not specify the type explicitly, by default float literals will have the type of f64
.
Arrays
mut nums := [1, 2, 3]
println(nums) // "[1, 2, 3]"
println(nums[1]) // "2"
nums[1] = 5
println(nums) // "[1, 5, 3]"
println(nums[0..2]) // slicing gives an array "[1, 5]"
println(nums.len) // "3"
nums = [] // The array is now empty
println(nums.len) // "0"
// Declare an empty array:
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
mut nums := [1, 2, 3]
nums << 4
println(nums) // "[1, 2, 3, 4]"
// append array
nums << [5, 6, 7]
println(nums) // "[1, 2, 3, 4, 5, 6, 7]"
mut names := ['John']
names << 'Peter'
names << 'Sam'
// names << 10 <-- This will not compile. `names` is an array of strings.
println(names.len) // "3"
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
):
arr := []int{len: 5, init: -1}
// `[-1, -1, -1, -1, -1]`
Setting the capacity improves performance of insertions, as it reduces the number of reallocations needed:
mut numbers := []int{ cap: 1000 }
println(numbers.len) // 0
// Now appending elements won't reallocate
for i in 0 .. 1000 {
numbers << i
}
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()
:
nums := [1, 2, 3]
nums_copy := nums.clone()
Arrays can be efficiently filtered and mapped with the .filter()
and .map()
methods:
nums := [1, 2, 3, 4, 5, 6]
even := nums.filter(it % 2 == 0)
println(even) // [2, 4, 6]
// filter can accept anonymous functions
even_fn := nums.filter(fn (x int) bool {
return x % 2 == 0
})
println(even_fn)
words := ['hello', 'world']
upper := words.map(it.to_upper())
println(upper) // ['HELLO', 'WORLD']
// map can also accept anonymous functions
upper_fn := words.map(fn (w string) string {
return w.to_upper()
})
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:
mut a := [][]int{len:2, init: []int{len:3}}
a[0][1] = 2
println(a) // [[0, 2, 0], [0, 0, 0]]
3d array example:
mut a := [][][]int{len:2, init: [][]int{len:3, init: []int{len:2}}}
a[0][1][1] = 2
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.
mut numbers := [1, 3, 2]
numbers.sort() // 1, 2, 3
numbers.sort(a > b) // 3, 2, 1
struct User { age int name string }
mut users := [User{21, 'Bob'}, User{20, 'Zarkon'}, User{25, 'Alice'}]
users.sort(a.age < b.age) // sort by User.age int field
users.sort(a.name > b.name) // reverse sort by User.name string field
Maps
mut m := map[string]int // Only maps with string keys are allowed for now
m['one'] = 1
m['two'] = 2
println(m['one']) // "1"
println(m['bad_key']) // "0"
println('bad_key' in m) // Use `in` to detect whether such key exists
m.delete('two')
// Short syntax
numbers := {
'one': 1
'two': 2
}