Skip to content

Control Flow

Jiang separates control-flow statements from expressions that produce values. Blocks, if, and switch can produce values; return, throw, break, and continue do not continue normally.

Int value = {
    Int base = 40;
    base + 2
}

A block’s value comes from its final tail expression. Without a tail expression, the block value is ().

An if condition is followed directly by a block. As a statement, else may be omitted:

if value < 0 {
    print("negative");
}

As an expression, if must include else and both branches must produce compatible values:

Int sign = if value < 0 {
    -1
} else {
    1
}

Statement-style branches can still include else:

if value < 0 {
    print("negative");
} else {
    print("non-negative");
}

switch matches patterns and can produce a value:

enum Mode {
    read,
    write,
}

Mode mode = Mode.read;

Int code = switch mode {
    .read => 1,
    .write => 2,
}

Branches may use a block:

Int code = switch mode {
    .read => {
        print("read");
        1
    },
    .write => 2,
}
Int count! = 0;
while count < 5 {
    count = count + 1;
}

break exits the current loop and continue starts the next iteration:

while count < 10 {
    count = count + 1;

    if count == 3 {
        continue;
    } else {
        print(count);
    }

    if count == 8 {
        break;
    }
}

for pattern in expr iterates arrays, slices, and other iterable values:

Int[_] values = [10, 20, 30];

for item in values {
    print(item);
}

Tuple destructuring is a separate statement inside the loop:

(Int, Int)[_] pairs = [(1, 2), (3, 4)];

for pair in pairs {
    (_ left, _ right) = pair;
    print(left + right);
}

Ranges use start..end:

for i in 0..10 {
    print(i);
}

guard is used for early exit while carrying successful bindings into the following scope:

Void print_value(Int? maybe) {
    guard maybe is .some(value) else {
        return ();
    }

    print(value);
    return ();
}

The else block must be non-empty and end in return, break, continue, or throw.

defer runs when the current block exits:

defer {
    unsafe {
        handle$.dealloc();
    }
}

defer {
    log("closing");
    unsafe {
        handle$.dealloc();
    }
}

Use defer expr; for a single operation or defer { ... } for several operations.

Use catch { ... } when only a fallback value is needed:

Int value = try parse(text) catch {
    0
};

Bind the error after catch when the handler needs it. The binding type is inferred from E:

Int value = try parse(text) catch err {
    log_error(err);
    0
};

Write the type explicitly as catch Error err { ... } when it improves clarity. Parenthesized bindings, such as catch (err) { ... }, are also accepted. The handler is always a block, and its tail expression is the fallback value.