Skip to main content

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:

if-else.stof
#[main]
fn main() {
  let value = 7;

  if (value > 10) pln("big");
  else if (value > 5) pln("medium");
  else pln("small");
}
Output

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:

switch.stof
#[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);
}
Output

while and loop

loop is shorthand for while (true) — expected to exit via break:

while-loop.stof
#[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);
}
Output

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:

for.stof
#[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);
}
Output

for...in also gives you three implicit variables inside the loop body — index, first, and last:

for-in.stof
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);
  }
}
Output

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:

tagged.stof
#[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);
}
Output

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:

try-catch.stof
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());
}
Output