Control Flow
Stof's control flow will look familiar — the differences worth knowing are which of these are statements and which can be used as expressions.
if / else
Braces are optional for a single-statement branch:
#[main]
fn main() {
let value = 7;
if (value > 10) pln("big");
else if (value > 5) pln("medium");
else pln("small");
}if is a statement, not an expression — it can't be the right-hand side of an assignment. For an inline conditional value, use the ternary operator instead, covered in Null & Initialization.
switch
No implicit fallthrough between cases — an empty case falls through to the next one, which is how you group multiple values into one branch. Unlike if, switch can be used as an expression:
#[main]
fn main() {
switch ('b') {
case 'a': pln("got a");
case 'b': {
pln("got b");
}
default: pln("other");
}
let res = switch ('yo') {
case 'yo': 42
case 'other': 100
default: 500
};
pln(res);
}while and loop
loop is shorthand for while (true) — expected to exit via break:
#[main]
fn main() {
let count = 5;
while (count > 0) {
count -= 1;
}
pln(count);
let tries = 0;
loop {
tries += 1;
if (tries >= 3) break;
}
pln(tries);
}for
The classic C-style form works, and so does for...in — over a number (counts from 0), a list, or anything with len() and at(index) methods:
#[main]
fn main() {
let total = 0;
for (let i = 0; i < 5; i += 1) {
total += i;
}
pln(total);
for (let i in 5) pln(i);
}for...in also gives you three implicit variables inside the loop body — index, first, and last:
names: ["Ada", "Grace", "Linus"]
#[main]
fn main() {
for (const name in self.names) {
if (first) pln("first:", name);
if (last) pln("last:", name);
pln(index, name);
}
}There's also a compact range literal for building a list directly — 0..10|2 (start..end|step) produces [0, 2, 4, 6, 8].
break, continue & Tagged Loops
Both work in every loop type. Prefix a loop with ^label to break/continue an outer loop from inside a nested one:
#[main]
fn main() {
let sum = 0;
for (let i = 0; i < 10; i += 1) {
if (i < 3) continue;
if (i > 6) break;
sum += i;
}
pln(sum);
let found = -1;
^outer for (let i = 0; i < 5; i += 1) {
for (let j = 0; j < 5; j += 1) {
if (i == 2 && j == 2) {
found = i * 10 + j;
break ^outer;
}
}
}
pln(found);
}Without ^outer, break on its own would only exit the inner for loop, and the outer loop would keep going.
try / catch
throw(value) throws any value, not just strings — catch (name: type) catches a specific type; a bare catch catches anything. Like switch, try/catch can be used as an expression:
fn risky(fail: bool) {
if (fail) throw('something broke');
}
fn attempt() -> int {
try 42
catch 72
}
#[main]
fn main() {
try { self.risky(true); }
catch (msg: str) { pln('caught:', msg); }
pln(self.attempt());
}