Control Flow
if / else
if temp_c > 80 {
fan.high()
} else if temp_c > 60 {
fan.medium()
} else {
fan.low()
}
No parentheses around conditions. Braces are always required.
if as an Expression
if can produce a value when both branches are present:
// Postfix form: for short single-expression cases
let label = "hot" if temp > 80 else "ok"
// Block form: when branches need statements
let delay_ms = if fast_mode {
let base = 10
base
} else {
let base = 100
base
}
Both branches must produce the same type. The expression if always requires an else.
if let
Lightweight alternative to match when you only care about one pattern:
if let Some(port) = config.get("port") {
log.info(port)
} else {
log.info("using default: 8080")
}
if let Ok(data) = file.read() {
process(data)
}
Works with Option, Result, and enum variants.
If you only care whether an Option has a value and do not need the inner binding, prefer is_some() or is_none().
If you only care whether a Result succeeded and do not need the success value, prefer is_ok() or is_err().
for
for iterates over collections, ranges, or any iterable value using the in keyword.
Collection iteration: iterate over every element in a list, array, or slice:
for item in items {
process(item)
}
Half-open range: iterates from the start up to but not including the end:
for i in 0..10 { // 0, 1, 2, ..., 9
sum += i
}
Inclusive range: iterates from start through and including the end:
for i in 0..=10 { // 0, 1, 2, ..., 10
sum += i
}
Discard the loop variable: use _ when you only need the iteration count:
for _ in 0..4 {
pulse()
}
Map iteration uses explicit view methods:
for key in settings.keys() { ... }
for value in settings.values() { ... }
for entry in settings.entries() {
log.info(entry.key)
apply(entry.value)
}
while
while not done {
step()
}
while bytes_read < target {
bytes_read += port.read(buf)?
}
loop
An explicit infinite loop. Used for MCU main loops and spin-wait patterns:
fn main() -> never {
loop {
let event = wait_for_event()
handle(event)
}
}
break and continue
for item in items {
if item == 0 {
continue // skip zeros
}
if item > MAX {
break // stop at first oversize item
}
process(item)
}
match
Covered in detail in the Match chapter. Brief overview:
match status {
Status.Ok => run()
Status.Pending => wait()
Status.Failed(e) => log.error(e)
}
match must be exhaustive. Every possible variant must be covered.
Operators and Precedence
Full precedence table from highest to lowest:
| Level | Operators |
|---|---|
| Postfix | (), [], ., ? |
| Prefix | unary -, not, ~ |
| Multiplicative | *, /, % |
| Additive | +, - |
| Shift | <<, >> |
| Bitwise AND | & |
| Bitwise XOR | ^ |
| Bitwise OR | | |
| Relational / membership | <, <=, >, >=, in |
| Equality | ==, != |
| Logical AND | and |
| Logical OR | or |
| Conditional | a if cond else b |
| Block if expression | if cond { ... } else { ... } |
match expression |
Assignment is always a statement, never an expression.
Standalone Blocks
Plain { ... } blocks create a new lexical scope. Useful for defer-scoped cleanup:
{
let guard = mutex.lock()
defer guard.release()
update_shared()
}
// guard released here
do_other_work()
return, break, and continue inside a block still target the enclosing function or loop.