Skip to content
You are reading next, which describes the unreleased compiler. Khora is unstable before v1: syntax, standard-library APIs and behavior may change. The current release is documented under v0.4.

Patterns

Patterns appear in match and catch arms, local destructuring, for bindings, and other positions that bind or inspect a value.

_

The wildcard matches a value without binding it. Prefer an explicit arm per variant where the cases mean different things: a wildcard earns its place when the remaining cases genuinely share a behavior, and costs something when they do not, because it is the one arm a newly added variant will silently fall into.

value

A bare identifier binds the matched value. value => .. matches anything and names it value.

A bare identifier that is the name of one of the value’s cases is an error. Over a Color, Red => .. would bind every color, not match Color::Red, so the compiler refuses it and names the pattern to write. This applies in every pattern (match, catch, let, for, and nested inside others) and to cases with a payload as well: NotFound => .. over an FsError is refused too. Write a constructor qualified (Color::Red, FsError::NotFound(path)), and bind with a name that is not a case.

The check is against the value’s type, not against what the file imports, so it applies to a value whose type the file never names. If a case is added to a type later, a binding that shares its name stops compiling. It does not silently start matching only that case.

A capitalized bare name is an error too, when it is no case of the value’s type. A name in a pattern that starts with a capital letter must be a case, so Gren => .. over a Color is refused, and the message offers the nearest case, Color::Green, when one is within two edits (or a third of the name’s length, if that is more). A const written as a pattern, FAVORITE => .., would bind every value rather than compare against the constant; compare with a guard instead:

match n {
n if n == FAVORITE => "lucky",
_ => "ordinary",
}

Bind with a lower-case name.

0
3.14
"ready"
true
false

Integer, floating-point, string, and boolean literals can be used as literal patterns.

Option::None
Status::Ready

A qualified path selects the named constructor.

Option::Some(value)
Result::Err(error)
Message::Move(x, y)

The patterns inside parentheses correspond to the constructor’s payload positions.

Shorthand field binding:

User { id, name }

Explicit nested pattern:

User {
id: user_id,
name: "admin",
}

General shape:

Path {
field,
field: Pattern,
...
}

A record pattern begins with a path and may bind fields by shorthand or supply another pattern after :.

(left, right)
(x, y, z)

Tuple patterns may nest other patterns:

(Result::Ok(value), _)
match result {
Result::Ok(value) => use_value(value),
Result::Err(error) => handle(error),
}

The compiler checks exhaustiveness and unreachable arms. Adding a variant to a type therefore turns every exhaustive match on it into a compile error, which is the list of places that need a decision about the new case.

A guard belongs to the arm after the pattern:

match score {
value if value >= 90 => "high",
value if value >= 50 => "medium",
_ => "low",
}

General form:

Pattern if BoolExpr => Expr

The guard runs only after the pattern itself matches.

let (left, right) = pair;
let User { id, name } = user;

A pattern used directly by let must be valid for the value’s type without requiring a missing-case branch. Refutable alternatives belong in match.

for entry in Dict::entries(table) {
print("${entry.key}: ${entry.value}");
}

The pattern binds each yielded item, under the same rules as let: valid for the item’s type, and irrefutable. Dict::entries yields a Pair, which is a record — a tuple pattern such as (key, value) is only valid where the item’s type is a tuple, and is refused otherwise.

for needs Step and Iterator in scope, and Pair too if the body reads the item’s fields: import std::core::{Dict, Iterator, Pair, Step, print};. See Control flow.

load_user(id)! catch {
UserError::NotFound(missing_id) => fallback(missing_id),
UserError::Unavailable(reason) => offline(reason),
}

catch reuses the pattern syntax but adds failure-row semantics: exhaustively handling a failure type removes that type from the failures that can leave the expression.

Patterns compose recursively:

match value {
Envelope {
payload: Result::Ok(User { id, name }),
} => use_user(id, name),
_ => fallback(),
}

Use nesting when it makes the shape clearer; split deeply nested business decisions into smaller matches when a single arm becomes difficult to read.

See Control flow for match result rules and Failures for catch semantics.

Khora 0.4.0built frombd964738afb6on 2026-10-17 20:41ZVersioningSecurityContributingReleasesSource