Control Flow
Move is imperative language and like one it has control flow - a way to make schoice whether to run block of code or to skip or to run another one instead.
In Move you have loops (while
and loop
) and if
expressions.
The if
expression
if
expression allows you to run a block of code if some condition is true, and to run another block instead if condition resulted in false.
script {
use 0x1::Debug;
fun main() {
let a = true;
if (a) {
Debug::print<u8>(&0);
} else {
Debug::print<u8>(&99);
};
}
}
In this example we've used if
+ block
to print 0
if a == true
and if a is false
- then 99
is printed. Simple as that, if syntax is:
if (<bool_expression>) <expression> else <expression>;
if
is an expression, and like all of them it must end with semicolon. This is also a reason to use it with let
statement!
script {
use 0x1::Debug;
fun main() {
// try switching to false
let a = true;
let b = if (a) { // 1st branch
10
} else { // 2nd branch
20
};
Debug::print<u8>(&b);
}
}
Now variable b
will be assigned a different value depending on the a
expression. But both of the branches in if
must return the same type! Otherwise variable b
will have an option to be of different kind (or undefined) and this is impossible in statically typed language. In compiler terms it's called branch compatibility - both of the branches must return compatible (same) type.
if
can be used in-solo - without else
.
script {
use 0x1::Debug;
fun main() {
let a = true;
// only one optional branch
// if a = false, debug won't be called
if (a) {
Debug::print<u8>(&10);
};
}
}
But keep in mind that if
expression without else
branch cannot be used in assignment as when condition is not met - alternative branch is not called and variable may be undefined which is, again, impossible.
Iterating with loops
There're two ways of defining loops in Move:
- Conditional loop with
while
- Inifinite
loop
Conditional loop with while
while
is a way to define loop - expression which will be executed while some condition is true. So simply: code will be run over and over while condition is true
. To implement condition usually an external variable (or counter) is used.
script {
fun main() {
let i = 0; // define counter
// iterate while i < 5
// on every iteration increase i
// when i is 5, condition fails and loop exits
while (i < 5) {
i = i + 1;
};
}
}
It's worth mentioning that while
is an expression - just like if
is, and it too requires a semicolon afterwards. Generic syntax for while loop is:
while (<bool_expression>) <expression>;
Unlike if
, while
cannot return a value, so variable assignment (like we did with if
expression) is impossible.
Unreachable code
To be reliable Move must be secure. This is why it obliges you to use all your variables and for the same reason it forbids having unreachable code. As digital assets are programmable, they can be used in code (you'll learn about it in resources chapter), and placing them in unreachable areas may lead to inconvenience and their loss as the result.
This is why unreachable code is such a big issue. Now that is clear, we can proceed.
Infinite loop
There is a way to define infinite loops. They're non-conditional and actually infinite (unless you force them to stop). Unfortunately the compiler cannot define whether a loop is infinite (in most of the cases) and cannot stop you from publishing code, execution of which will consume all given resources (in blockchain terms - gas). So it's on you to test your code properly when using them or just switch to conditional while
as it's way more secure.
Infinite loops are defined with keyword loop
.
script {
fun main() {
let i = 0;
loop {
i = i + 1;
};
// UNREACHABLE CODE
let _ = i;
}
}
However this is possible (compiler will let you do this):
script {
fun main() {
let i = 0;
loop {
if (i == 1) { // i never changed
break // this statement breaks loop
}
};
// actually unreachable
0x1::Debug::print<u8>(&i);
}
}
It's a non-trivial task for the compiler to understand whether a loop is really infinite or not, so for now you and only you can help yourself avoid looping errors. As I described above, this can lead to assets loss.
Control loops with continue
and break
Keywords continue
and break
allow you to skip one round or break iteration respectively. You can use both of them in both types of loops.
For example let's add two conditions into the loop
. If i
is even, we use continue
to jump to the next iteration without going through code after continue
call.
With break
we stop iteration and exit loop.
script {
fun main() {
let i = 0;
loop {
i = i + 1;
if (i / 2 == 0) continue;
if (i == 5) break;
// assume we do something here
};
0x1::Debug::print<u8>(&i);
}
}
About semicolons. If break
and continue
are the last keywords in block, you can't put a semicolon after them as any code after won't be executed. Somehow even semi can't be put. See this:
script {
fun main() {
let i = 0;
loop {
i = i + 1;
if (i == 5) {
break; // will result in compiler error. correct is `break` without semi
// Error: Unreachable code
};
// same with continue here: no semi, never;
if (true) {
continue
};
// however you can put semi like this, because continue and break here
// are single expressions, hence they "end their own scope"
if (true) continue;
if (i == 5) break;
}
}
}
Conditional abort
Sometimes you need to abort execution of transaction when some condition has failed. For that case there's keyword abort
.
script {
fun main(a: u8) {
if (a != 10) {
abort 0;
}
// code here won't be executed if a != 10
// transaction aborted
}
}
Keyword abort
allows you to abort execution with an error code which is placed right after.
Use assert
built-in
Built-in assert(<condition>, <code>)
method already wraps abort
+ condition and is accessible anywhere in code:
script {
fun main(a: u8) {
assert(a == 10, 0);
// code here will be executed if (a == 10)
}
}
assert()
will abort execution when condition is not met, or it will do nothing in the opposite case.