Skip to main content

Control Flow

If/else

All control flow examples in this chapter are inside a function.

if (balance >= amount) {
balance -= amount;
} else {
// handle insufficient balance
}

Conditions must be bool. Ora does not implicitly convert integers to booleans — if (x) is a type error; write if (x != 0).

Chained conditions:

if (score >= 90) {
return 4;
} else if (score >= 80) {
return 3;
} else if (score >= 70) {
return 2;
} else {
return 1;
}

While loops

var i: u256 = 0;
while (i < 10) {
i += 1;
}

While loops can carry an invariant clause for formal verification (covered in Chapter 10):

while (i < limit)
invariant(i <= limit)
{
i += 1;
}

For loops

Range iteration

for (0..10) |value, index| {
// value and index both go from 0 to 9
}

0..10 is a half-open range: includes 0, excludes 10. The |value, index| syntax captures the loop value and its index.

Use _ to discard a capture:

for (0..5) |i, _| {
// only use i, discard the index
}

Array iteration

let numbers: [u256; 5] = [1, 2, 3, 4, 5];
for (numbers) |value, index| {
// value is the element, index is the position
}

Nested loops

for (0..3) |i, _| {
for (0..3) |j, _| {
// i and j available here
}
}

Break and continue

var result: u256 = 0;
for (0..100) |i, _| {
if (i == 50) {
break; // exit the loop
}
if (i % 2 == 0) {
continue; // skip to next iteration
}
result += i;
}

Labeled blocks

Labeled blocks let you break out of specific scopes:

outer: {
var j: u256 = 0;
while (j < 10) {
j += 1;
if (j == 5) {
break :outer; // exit the labeled block, not just the loop
}
}
}

Switch

Switch works as both a statement and an expression.

Switch statement

switch (status) {
0 => {
handlePending();
},
1 => {
handleActive();
},
2 => {
handleCompleted();
},
else => {
handleUnknown();
},
}

Range patterns

switch (score) {
0...59 => {
return 0;
},
60...79 => {
return 1;
},
80...100 => {
return 2;
},
else => {
return 0;
},
}

0...59 is an inclusive range: matches 0 through 59.

Switch expression

Switch can be used as an expression that produces a value:

let tier: u256 = switch (amount) {
0...999 => 1,
1000...9999 => 2,
else => 3,
};

No fallthrough

Switch arms do not fall through. Each arm is independent — no break needed.

The vault with control flow

Adding a classification function to our vault:

pub fn depositTier(amount: u256) -> u256 {
return switch (amount) {
0...999 => 1,
1000...9999 => 2,
10000...99999 => 3,
else => 4,
};
}

And improving withdraw with a balance check:

pub fn withdraw(amount: u256) -> bool {
let sender: address = std.msg.sender();
let current: u256 = balances[sender];
if (current < amount) {
return false;
}
balances[sender] = current - amount;
totalDeposits -= amount;
return true;
}

This is better than the original (which had no check), but still not ideal — returning false on failure hides the reason. In Chapter 6, we'll use error unions to make failure explicit in the type system.

Further reading

  • Switch — complete switch reference with all pattern types