Control flow
Mochi has a small set of control-flow constructs: if for conditionals,
for and while for loops, and match for pattern matching. if also
works as an expression, so short branches read cleanly without intermediate
variables.
if, else if, else
The statement form has a brace body for each branch:
if temperature < 0 {
print("freezing")
} else if temperature < 20 {
print("mild")
} else {
print("warm")
}
The condition must have type bool. Mochi has no implicit truthiness, so
if 0 is a compile-time error. Convert numbers and strings explicitly:
if items.len() > 0 { ... } // ok
if items.len() { ... } // error: condition is not bool
Branches are blocks. Each block has its own scope. let bindings declared
inside one branch are not visible outside.
if as an expression
In a value position, if returns the value of the chosen branch. Use
then and else:
let label = if score >= 50 then "pass" else "fail"
Both branches must produce a value of the same type (or types that combine into a known union). The expression form chains naturally:
let grade =
if score >= 90 then "A"
else if score >= 80 then "B"
else if score >= 70 then "C"
else "F"
For longer expression-form branches, wrap each branch in { ... } and end
with the value:
let summary = if user.is_admin {
let role = "administrator"
let last = user.last_login
role + " (last seen " + str(last) + ")"
} else {
"regular user"
}
for loops
The general form is for x in iterable:
for i in 1..4 {
print(i) // 1, 2, 3
}
for x in [10, 20, 30] {
print(x)
}
for ch in "hello" {
print(ch)
}
for (k, v) in {"a": 1, "b": 2} {
print(k, v)
}
Ranges
a..b produces a half-open range from a (inclusive) to b (exclusive).
a..=b is inclusive on both ends.
for i in 0..3 { print(i) } // 0, 1, 2
for i in 0..=3 { print(i) } // 0, 1, 2, 3
Ranges are values. Store them in a binding or pass them to any function that takes an iterable.
break and continue
break exits the loop. continue skips to the next iteration.
for n in 1..20 {
if n % 7 == 0 { break }
if n % 2 == 0 { continue }
print(n)
}
while loops
var n = 0
while n < 5 {
print(n)
n = n + 1
}
The condition must be bool and is evaluated before each iteration. Mochi
has no do-while. To run the body once before checking, place the body
and condition explicitly:
var attempt = 0
while true {
if try_fetch() { break }
attempt = attempt + 1
if attempt >= 3 { break }
}
break and continue work the same as in for.
match expressions
match performs pattern matching across literal values, type narrowings,
and union variants. Each arm is checked in order; the first match wins.
fun classify(n: int): string {
return match n {
0 => "zero",
1 => "one",
n if n < 0 => "negative",
_ => "many"
}
}
Arms separate the pattern from the body with =>. A guard (if ...)
optionally constrains the match. _ is the wildcard.
Patterns
| Pattern | Matches |
|---|---|
42 | The literal 42. |
"abc" | The literal string. |
name | Any value, binding it to name. |
_ | Any value, discarded. |
Variant | A no-arg union variant. |
Variant(x, y) | A union variant, binding the constructor arguments. |
[x, y, ...rest] | A list with at least two elements, head bound. |
{"key": v} | A map with the given key, value bound. |
n: int | A value with type narrowed to int. |
Pattern matching on unions
type Tree =
Leaf
| Node(value: int, left: Tree, right: Tree)
fun sum(t: Tree): int {
return match t {
Leaf => 0,
Node(v, l, r) => v + sum(l) + sum(r)
}
}
The exhaustiveness checker warns when a variant is missing. Suppress the
warning with a _ => ... catch-all when intentional.
Pattern matching on optional values
fun greet(name: string | nil): string {
return match name {
nil => "anonymous",
n => "hello, " + n
}
}
Guards
match score {
s if s >= 90 => print("A"),
s if s >= 80 => print("B"),
_ => print("C or below")
}
Guards are arbitrary boolean expressions. They run only when the structural pattern already matches.
Control flow with collections
for over a list pairs naturally with from / where / select. The query
form is often cleanest, with a loop for side effects:
let unread = from b in books where !b.read select b
for b in unread {
print(b.title)
}
Read more in datasets.
Short-circuit evaluation
&& and || are short-circuiting. The right operand is evaluated only when
needed.
if user != nil && user.is_admin {
...
}
This is the idiomatic pattern for combining an existence check with a follow-on property access.
Common errors
| Message | Cause | Fix |
|---|---|---|
condition is not bool | if or while with a non-bool | Use an explicit comparison. |
match is not exhaustive | Missing variant or fall-through | Add a branch or _ => .... |
unreachable code after match | A previous arm covers everything | Remove the trailing branch. |
break outside loop | break in a match arm | Use return or restructure. |