Skip to main content
Kairo keeps control flow explicit and honest. There is no truthy/falsy coercion — branching conditions must be Bool. Pattern matching with match handles both simple branching and exhaustive case analysis, and it works as either a statement or an expression. Local bindings distinguish mutable state (var) from immutable state (let) at the point of declaration. The result is code where the intent of every branch and binding is visible from the syntax alone.

Local Bindings: let and var

Declare a local variable with let (immutable) or var (mutable). let bindings cannot be reassigned after their initial value is set.
For let, Kairo can often infer the type when the initializer makes it obvious. In cases where the type is not immediately clear, an explicit annotation is clearer to both the compiler and the reader. var declares a binding that you intend to reassign. In v0.1, var local bindings require an explicit type annotation.
Prefer let for all bindings by default. Reach for var only when you genuinely need to reassign the binding later in the same scope. Unnecessary mutability makes code harder to reason about.

if / else

Use if and else for boolean branching. The condition must be a Bool expression — Kairo does not coerce non-boolean values to true or false.
Chain multiple conditions with else if:
Kairo does not perform truthy/falsy coercion. Writing if someText { ... } or if someObject { ... } is a compile error. Your condition must evaluate to a Bool.
You can use not to negate a boolean expression:

match — Pattern Matching

match is Kairo’s primary tool for exhaustive branching. It works in two forms: a statement form that executes blocks, and an expression form that produces a value.

Statement Form

Use the statement form when each branch performs actions or side effects. Each arm uses a colon followed by a block:

Expression Form

Use the expression form when each branch produces a value and you want to bind the result. Each arm uses => and the else arm provides the fallback:
The expression form eliminates intermediate var bindings that exist only to be assigned in branches. Wherever a match produces a single type in all arms, prefer the expression form.

Matching on Nullable Values

Use null as a pattern to handle the absent case of a nullable type:

Matching on Boolean Results

The canonical way to inspect a Result<T, E> is to match on its .isSuccess property:

The else Arm

Every match that does not enumerate all possible cases must include an else arm as a catch-all. For enum types with a known set of cases, you can omit else if every case is listed explicitly — but the compiler will warn if cases are missing.

return Statement

Use return to exit the current function and optionally produce a value. return can appear anywhere in a function body — execution of the function stops immediately.
In a function with no return type, use a bare return to exit early:

async / await

async and await are Kairo’s explicit suspension mechanism. Declare a function async to allow it to suspend, and use await at each suspension point inside it.
await is only valid inside an async function. There is no implicit async promotion — a regular function cannot silently suspend.
async / await are covered in detail in the Functions page. They are listed here because await is an expression that participates in control flow — it suspends the current function until the awaited operation completes, then resumes from that point.

Loops: for and while (Reserved in v0.1)

for and while are reserved keywords in Kairo v0.1. Loop semantics are defined in the language specification but are not yet available in this version. If you need to iterate over a collection, use collection methods or recursive func patterns in the meantime.
for and while are intentionally reserved in v0.1. Their semantics — including how they interact with immutable collections and Result-returning closures — will be introduced in a future release. Using either keyword as an identifier is a compile error.

Quick Reference