Control Flow and Pattern Matching
Control flow in Ferlium is expression-based: if and match both produce values. This chapter explains how their types are determined and how to use them safely and clearly.
if is an expression
An if expression always produces a value. When both branches are present, the result is the value of the selected branch.
let x = if 1 < 2 { 10 } else { 20 };
x
The condition must be a bool. Both branches must produce compatible types, because the whole if has a single type.
if true { 1 } else { 2 } // ok: int
if true { "yes" } else { "no" } // ok: string
Branch types must agree
If the branches have different types, the program does not type check:
if true { 1 } else { "no" } // type error
if without else
An if without else is allowed when it is used for its effect. In that case, its result type is ().
let mut a = 0;
if true { a = 1 };
a
Trying to use a value-producing branch without an else is a type error:
// type error: missing else for a non-unit branch
fn f() { if true { 1 } }
match is an expression
A match expression selects one of several branches based on a value. Like if, it produces a value.
In this chapter, we only use literal patterns and the wildcard _.
let a = 0;
match a {
0 => 1,
1 => 2,
_ => 3,
}
An entry LITERAL => EXPRESSION is called an arm.
All arms must have compatible types
Every arm in a match must produce a value of the same type, because the whole match has a single type:
match 0 {
0 => 1,
_ => 2,
}
This is a type error:
match 0 {
0 => 1,
_ => "no",
}
Exhaustiveness with _
When matching on literals, use _ as the default case unless you explicitly cover all possible values. For example, matching on bool can list both cases:
match true {
true => 1,
false => 0,
}
Early return
A function normally returns the value of its body block. Use return when you need to leave the function early:
fn clamp_non_negative(x: int) -> int {
if x < 0 { return 0 };
x
}
clamp_non_negative(-3)
The returned expression must have the function’s return type. A return exits the current function or lambda, even when it appears inside a nested block, if, or match arm.
let f = |x| {
if x { return 1 };
2
};
f(true)
Loop expressions
Use loop when the stopping condition lives inside the body.
A break exits the loop, and continue skips to the next iteration.
let mut n = 0;
loop {
n += 1;
if n < 3 { continue };
break n
}
A loop is an expression.
Its result type comes from the values passed to break; a bare break is the same as break ().
let answer = loop {
break 42
};
If a loop has no reachable break, its type is never.
Loop labels
Labels let break and continue target an outer loop directly:
let mut outer = 0;
let mut inner = 0;
'outer: loop {
outer += 1;
if outer == 3 { break inner };
loop {
inner += 1;
continue 'outer
}
}
The label is written before the target loop, followed by :, and reused after break or continue.
What comes next
Later chapters expand pattern matching to structured data and more powerful patterns. For now, you can use literals and _ to write clear, type-safe control flow.