Statements & expressions
If
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 expressions
Unlike C, V does not have a ternary operator, that would allow you to do: x = c ? 1 : 2 .
Instead, it has a bit more verbose, but also clearer to read, ability to use if as an
expression. The direct translation in V of the ternary construct above, assuming c is a
boolean condition, would be: x = if c { 1 } else { 2 }.
Here is another example:
You can use multiple statements in each of the branches of an if expression, followed by a final
value, that will become the value of the entire if expression, when it takes that branch:
If unwrapping
Anywhere you can use or {}, you can also use "if unwrapping". This binds the unwrapped value
of an expression to a variable when that expression is not none nor an error.
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:
or using match:
This works also with struct fields:
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:
Match
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.
A match statement can also to be used as an if - else if - else alternative:
or as an unless alternative: unless Ruby
A match expression returns the value of the final expression from the matching branch.
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.
A match statement also can match the variant types of a sumtype. Note that
in that case, the match is exhaustive, since all variant types are mentioned
explicitly, so there is no need for an else{} branch.
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.
Constants can also be used in the range branch expressions.
[!NOTE]
matchas an expression is not usable inforloop andifstatements.
In operator
in allows to check whether an array or a map contains an element.
To do the opposite, use !in.
[!NOTE]
inchecks if map contains a key, not a value.
It's also useful for writing boolean expressions that are clearer and more compact:
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
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:
By default, array elements are taken by value, if you need elements to be taken by
reference, use & on the array you want to iterate over:
The same applies to maps.
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.
The code above prints:
1
4
9
16
25
Map for
Either key or value can be ignored by using a single underscore as the identifier.
Range for
low..high means an exclusive range, which represents all values
from low up to but not including high.
[!NOTE] This exclusive range notation and zero-based indexing follow principles of logical consistency and error reduction. As Edsger W. Dijkstra outlines in 'Why Numbering Should Start at Zero' (EWD831), zero-based indexing aligns the index with the preceding elements in a sequence, simplifying handling and minimizing errors, especially with adjacent subsequences. This logical and efficient approach shapes our language design, emphasizing clarity and reducing confusion in programming.
Condition for
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
The condition can be omitted, resulting in an infinite loop.
C for
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:
The label must immediately precede the outer loop. The above code prints:
4
5
6
7
Defer
A defer {} statement, defers the execution of the block of statements
until the surrounding scope of the defer ends. It is a convenient feature
that allows you to group related actions (getting access to a resource
and cleaning/freeing it after you are done) closely together, instead
of spreading them across multiple potentially very remote lines of code.
If the function returns a value the defer block is executed after the return
expression is evaluated:
To access the result of the function inside a defer block the $res() expression can be used.
$res() is only used when a single value is returned, while on multi-return the $res(idx)
is parameterized.
defer in loop scopes:
Defer can be used inside loops too, and the deferred statement will be executed once for each iteration. You can also have multiple defer statements in the same scope, in which case, they will be executed in reverse order of their appearance in the source code:
The example will print this:
Loop start.
Loop iteration: 1
Deferred execution for 1. Defer 3.
Deferred execution for 1. Defer 2.
Deferred execution for 1. Defer 1.
Loop iteration: 2
Deferred execution for 2. Defer 3.
Deferred execution for 2. Defer 2.
Deferred execution for 2. Defer 1.
Loop iteration: 3
Deferred execution for 3. Defer 3.
Deferred execution for 3. Defer 2.
Deferred execution for 3. Defer 1.
Loop done.
Program finish.
defer(fn) {}
Note, that in most of the examples above, the defer{} statement was directly inside
a function scope, so it was executed when the function itself returned. Sometimes, you
need to defer a statement to execute right at the function end (like the above), even
if you are inside an inner scope (deep inside an if or for).
For these more rare cases, you can use: defer(fn) {} instead of just defer {}.
Goto
V allows unconditionally jumping to a label with goto. The label name must be contained
within the same function as the goto statement. A program may goto a label outside
or deeper than the current scope. goto allows jumping past variable initialization or
jumping back to code that accesses memory that has already been freed, so it requires
unsafe.
goto should be avoided, particularly when for can be used instead.
Labelled break/continue can be used to break out of
a nested loop, and those do not risk violating memory-safety.