Skip to main content

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

PatternMatches
42The literal 42.
"abc"The literal string.
nameAny value, binding it to name.
_Any value, discarded.
VariantA 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: intA 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

MessageCauseFix
condition is not boolif or while with a non-boolUse an explicit comparison.
match is not exhaustiveMissing variant or fall-throughAdd a branch or _ => ....
unreachable code after matchA previous arm covers everythingRemove the trailing branch.
break outside loopbreak in a match armUse return or restructure.

Next

  • Functions: closures, defaults, varargs
  • Types: structs, unions, methods
  • Errors: optional values, expect, panic
  • Datasets: from / where / select