Control Flow

Zith's control flow combines familiar C-like constructs with pattern matching, chain flow, and flow functions.

Syntax Rules

Parentheses () are mandatory on all control structure conditions except function calls. Logical operators use English keywords; bitwise use standard symbols followed by ..

if (x > 0 and y < 10) { ... }
if isTrue() and (x > 5) { ... }
let mask = a &. b |. c ^. d;

if / else

if (x > 0) {
    @println("positive");
} else (x < 0) {
    @println("negative");
} else {
    @println("zero");
}

// As expression
let sign = if (x > 0) { "pos" } else { "neg" };

for — Loop Constructs

for { ... }                                    // infinite
for (i in 0..=9) { @println(i); }              // inclusive range
for (i in 0..9)  { @println(i); }              // exclusive range
for (i = 0), (i < 10), (i += 1) { ... }        // init / cond / step
for (v in range(0, 100)) { @println(v); }      // over a generator

// Destructure group with fallback
let r = for ([acc, i]: i32), (i in 0..n) { acc *= i + 1 } or 0;

ℹ Note: If the loop may not run, the return is deduced as optional — unless or collapses it to non-optional.

when — Pattern Matching

when (count) {
    0       = @println("none"),
    1       = @println("one"),
    2..10   = @println("few"),
    _       = @println("many"),
}

// As expression
let label = when (score) { 90..100 = "A", 70..90 = "B", _ = "C" };

// With branch tags
when (v) {
    n: i32  = @println("int: {n}"),
    f64     = @println("float: {v}"),
    _       = @println("other"),
}

Chain Flow (->)

The -> operator pipes output left-to-right. The previous value is ... Tags capture values for later use.

getData() -> process(..) -> save(..);

getData()
    -> raw:    parse(..)
    -> parsed: validate(..)!       // ! propagates out
    -> connectDb()
    -> save(parsed);

// Inline block
readFile("data.bin")
    -> { let h = parse_header(..); validate(h)! }
    -> process_body(..);

flow fn & Markers

Flow functions enable cooperative multitasking through marker, dock, and jump.

flow fn run(data: Stream): void {
    marker Process(chunk: Chunk, count: i32) {
        transform(chunk);
    }

    let i = 0;
    for item in data {
        dock { jump Process(item, i); }
        i += 1;
    }
}

// Global marker
marker ContextSwitch(next: TaskId) {
    saveRegisters();
    loadTask(next);
}

// noreturn: dock return variable never modified
flow fn scheduler(): noreturn { ... }

Marker Rules

Stackful Markers

By default, markers are stackless. The stackful modifier opts into stackful behaviour: when a jump is executed from inside a stackful marker, all local owned nodes are dropped before the jump.

flow fn run(data: Stream): void {
    stackful marker Process(chunk: Chunk) {
        let buffer = allocate(chunk.size);  // owned — dropped before jump
        transform(buffer);
    }
}

See also: Spec: Control Flow


Next: Memory Model — NRA