Control flow
Khora’s control-flow forms are expressions or block-like expressions. This page records their accepted shapes and result rules.
Blocks
Section titled “Blocks”{ let value = compute(); value + 1}General shape:
{ Statement* Expr?}The final expression, when present without a semicolon, is the block’s value. A block with no final value produces ().
if condition { when_true} else { when_false}else if is nested if syntax:
if first { a} else if second { b} else { c}The condition must be Bool. When an if is used as a value, its branches must agree on a result type. An if without else must produce ().
match value { Pattern => Expr, Pattern if guard => Expr,}An arm body may be a block:
match value { Result::Ok(item) => { log(item); item }, Result::Err(error) => fallback(error),}Patterns are checked for exhaustiveness and reachability. See Patterns.
Match guards
Section titled “Match guards”match score { value if value >= 90 => "high", value if value >= 50 => "medium", _ => "low",}The guard runs only after its pattern matches and must produce Bool.
while condition { body;}The condition must be Bool.
for pattern in iterable { body;}The left side is a pattern. Khora has a tuple literal — see
Tuple and unit expressions — but no standard
iterator yields one: an iterator over pairs yields a Pair record, so the loop
binds it by name and reads its fields.
for entry in Dict::entries(table) { print("${entry.key}: ${entry.value}");}Pair<K, V> has the fields key and value, and String::split_once
answers a Split with the fields head and rest. Both are ordinary records:
they are read with ., and written as record literals — let p: Pair<String, Int> = { key: "a", value: 1 };.
A tuple pattern — for (key, value) in ... — is only valid against a value
whose type really is a tuple, and is refused against a Pair:
error: this pattern takes a value apart into 2 pieces, but `Pair<String, Int>` is not a tuplefor desugars to Iterator::next, so both Iterator and Step must be in scope in the module that writes the loop:
import std::core::{Iterator, Step};A tuple is taken apart by pattern and never by position: there is no pair.0.
Destructure it in the for, in a let, or in a match arm.
let point = (3, 4);let (x, y) = point;Reach for a transform such as List::map when the result is another
collection, and for for when the point is the body run once per element.
loop { body;}A loop exits through break, return, raise, failure propagation, cancellation, or other non-local control flow.
Without a value:
break;With a value:
break result;A loop with value-carrying break expressions produces the common type of those values:
let found = loop { let candidate = next(); if candidate.valid { break candidate; }};continue
Section titled “continue”continue;continue skips the remainder of the current loop iteration.
return
Section titled “return”Return a value:
return value;Return unit:
return;A function normally returns its body’s final expression; return is the
explicit early-exit form. Prefer the final expression where it stays clear, and
return where an early exit is what makes the control flow readable.
Typed failure control flow
Section titled “Typed failure control flow”Explicit raise:
raise UserError::NotFound(id)Propagate a fallible call:
load_user(id)!Handle a typed failure:
load_user(id)! catch { UserError::NotFound(_) => User::guest(), UserError::Unavailable(reason) => User::offline(reason),}raise is not return. It leaves through the function’s typed failure
channel, which is a second exit the signature declares separately; !
propagates that channel and catch handles it before it reaches the caller.
See Failures.
Capability installation as control scope
Section titled “Capability installation as control scope”Postfix installation serves one expression:
load_user(id)! with { store: test_store,}Block installation serves a lexical region:
with Production { run_server()!}See Capabilities for context rows and overrides.
Khora 0.2.0built frombd964738afb6on 2026-10-17 20:41ZVersioningSecurityContributingReleasesSource