Control Flow

Branching and looping. Most control-flow constructs are expressions in Kestrel — they produce a value — which means you can use them on the right side of let.

If / Else

let isAdmin = false; let isMember = true; if isAdmin { println("full access"); } else if isMember { println("member access"); } else { println("no access"); }

Because if is an expression, you can assign its result:

let score = 85; let label = if score > 90 { "excellent" } else if score > 70 { "good" } else { "needs work" };

Each branch must produce a value of the same type. Unless one of them diverges — calls a !-returning function, breaks, or returns — in which case it's allowed to be a different shape, since execution will never get past it. See Functions → Return Types.

Loops

while runs as long as a condition holds:

var i = 0; while i < 10 { println("\(i)"); i = i + 1; }

loop is unconditional — exit it with break:

var countdown = 3; loop { if countdown == 0 { break; } println("\(countdown)"); countdown = countdown - 1; }

for walks an iterable:

let prices = [12, 30, 7]; var total = 0; for price in prices { total = total + price; }

continue skips to the next iteration. break exits the loop. Both can target an outer loop with a label:

let rows = [[1, 2], [3, 4]]; let target = 3; search: for row in rows { for cell in row { if cell == target { break search; } } }

Guard

guard is the early-exit pattern. It checks a condition and, if it fails, runs an else block that must leave the surrounding scope (return, throw, break, or call a !-returning function).

enum ParseError { case EmptyInput case ParseFailed } func parse(input: String) -> Optional[Int] { if input == "42" { .Some(42) } else { .None } } func transform(n: Int) -> Int { n * 2 } func process(input: String) -> Result[Int, ParseError] { guard not input.isEmpty else { return .Err(.EmptyInput); } guard let .Some(parsed) = parse(input) else { return .Err(.ParseFailed); } // parsed is in scope here, unwrapped .Ok(transform(parsed)) }

Compared to if, guard keeps the happy path at the outer indentation level. Use it when "the rest of this function depends on this being true."

Match

match checks a value against patterns. The first matching arm runs.

enum Status { case Active case Paused(String) case Stopped } let status = Status.Paused("rebalancing"); match status { .Active => println("running"), .Paused(reason) => println("paused: \(reason)"), .Stopped => println("done"), }

Like if, match is an expression — every arm produces a value:

<!-- sample: continue --> let label: String = match status { .Active => "live", .Paused(_) => "halted", .Stopped => "off" };

The compiler checks that every case is handled. Add a new variant to the enum and every existing match lights up red until you cover it. Use _ as a catch-all when you genuinely want to default; don't use it to silence the exhaustiveness checker. For the deeper pattern-matching story — destructuring, guards, bindings — see Enums → Pattern Matching.