machdocs
Home GitHub

Control flow

$if and $or branch on comptime-evaluable conditions. Only the taken arm is compiled; the discarded arms are never resolved, type-checked, or emitted - unlike runtime if / or, which always generates a branch.

The $if / $or form

A $if chain mirrors runtime if / or in shape, but each gate is a comptime condition and only the selected branch reaches the binary. $or with a condition is an else-if; $or with no condition is the comptime else.

$if (cond) {
    ...
}
$or (cond) {
    ...
}
$or {
    ...                 # comptime else
}

Target gating is the most common use: select the right import or backend per build, and leave the rest out of the binary entirely.

$if ($mach.build.os == $mach.os.linux) {
    use full.os.linux;
}
$or ($mach.build.os == $mach.os.windows) {
    use full.os.windows;
}
$or {
    $error("unsupported OS");
}

Comptime conditions

The gate must be a comptime expression. Common shapes:

A comptime comparison or arithmetic relates the mathematical values of its operands, exactly as the runtime operators do. A constant in the range 2^63 to 2^64 - 1 carries its true unsigned magnitude, so $if (0xFFFFFFFFFFFFFFFF > 0) is taken, and any cross-sign comparison agrees with the runtime form: $if (X < Y) never selects a branch that if (X < Y) would not.

Note

Comptime arithmetic that overflows the value's range is a compile error, not a silent wrap.

Branching on a comptime parameter

A comptime function parameter ($mode: u8) is a compile-time-known argument, fixed per call site. $if / $or may branch on it: the compiler monomorphizes the body once per distinct comptime-argument value, and each instance compiles only the arm its value selects.

val MODE_DOUBLE: u8 = 0;
val MODE_SQUARE: u8 = 1;

fun apply($mode: u8, n: i64) i64 {
    $if (mode == MODE_DOUBLE) {
        ret n + n;
    }
    $or (mode == MODE_SQUARE) {
        ret n * n;
    }
    ret 0;
}

# apply(MODE_DOUBLE, ..) and apply(MODE_SQUARE, ..) emit two distinct bodies,
# each carrying only its selected arm.

Rules

Because each instance compiles only its taken arm, a comptime parameter can safely gate per-target asm blocks: a register one backend does not recognize lives only in the arm the other backend never compiles.

pub fun load($order: Order, ptr: *i64) i64 {
    var result: i64 = 0;

    $if ($mach.build.arch == $mach.arch.aarch64) {
        $if (order == RELAXED) {
            asm aarch64 { ldr {result}, [{ptr}] }
        }
        $or (order == ACQUIRE) {
            asm aarch64 { ldar {result}, [{ptr}] }
        }
    }

    ret result;
}

Discarded branches

An untaken $if branch is absent from the compiled output, but what "not taken" means for name resolution depends on what the gate reads.

See also