Markdown 215 lines
# Chapter 8: Control Flow & Loops
Flash has a clean set of control structures, matching Go's syntax structure with Zig's powerful optional unwrapping and compile-time unrolling.
---
## 1. Conditionals (`if` / `else`)
In Flash, braces `{}` are mandatory, even for single-statement blocks. Parentheses around the condition are optional.
```flash
if value > 100 {
log("High")
} else {
log("Low")
}
```
### Optional Unwrapping in `if`
If an expression returns an optional type (`?T`), you can unwrap it in an `if` block using the vertical pipe syntax `|value|`. The block only executes if the value is not `null`.
```flash
// If lookup returns a user record, bind it to 'user' and print
if lookupByUid(uid) |user| {
print(user.name)
} else {
print("User not found")
}
```
### Value-form `if` Expressions
`if` can also be used as an expression (resembling a ternary operator in other languages) to return values. In this form, the `else` branch is mandatory.
```flash
// Parens around the condition are required here to avoid parser ambiguity
max := if (a > b) a else b
```
---
## 2. While Loops
The `while` loop runs as long as a condition evaluates to `true`.
```flash
var i usize = 0
while i < 10 {
print_num(i)
i += 1
}
```
### While with Capture
When the loop condition yields an optional (`?T`), a `|value|` capture binds the payload on each turn. The loop runs until the expression yields `null` — the idiomatic way to drain an iterator:
```flash
// Pull items until the iterator is exhausted
while it.next() |item| {
process(item)
}
```
### While with `orelse break`
You can run a loop and break out when a value resolves to `null` using `orelse break`:
```flash
var index usize = 0
while index < argc {
arg := argv[index] orelse break
print(arg)
index += 1
}
```
### Inline While Loops (`inline while`)
An `inline while` loop is unrolled at compile time. This is particularly useful in comptime blocks or function generation to loop over fixed-size values.
```flash
comptime var i = 0
inline while i < 4 {
// This loop is unrolled at compile time
emit(i)
i += 1
}
```
---
## 3. For Loops (`for ITEM in ITER`)
Flash utilizes a Go-flavored `for ITEM in ITER` layout. Under the hood, this lowers directly to Zig's `for (iter) |item|`.
```flash
var msg = "hello"
// Loops over each character in the string slice
for char in msg {
print_char(char)
}
```
### Ranges
A numeric range `lo..hi` iterates the half-open interval `[lo, hi)` — the upper bound is excluded:
```flash
// Prints 0, 1, 2, … 9
for i in 0..10 {
print_num(i)
}
```
### Index Capture
A second capture name gives the zero-based iteration index alongside the element:
```flash
// item is each element; idx counts 0, 1, 2, …
for item, idx in items {
print_kv(idx, item)
}
```
Discard either capture you do not need with `_` (for example `for _, idx in items` to walk the indices only).
### Loop `else` Arms
A `while` or `for` loop may carry an `else` arm, run when the loop ends **without** hitting a `break` — the search-loop idiom without a flag variable:
```flash
for x in xs {
if x == needle {
break
}
} else {
log("needle is not in xs")
}
```
### Inline For Loops (`inline for`)
Like `inline while`, an `inline for` loop is unrolled at compile time — the body is stamped out once per element. Every `for` shape rides along: the range iterator, the indexed second capture, the `_` discard, and the loop `else` arm.
```flash
inline for w in .{ 8, 16, 32 } {
emit(w)
}
```
---
## 4. Switch
A `switch` matches a subject value against one or more prongs. The braces and an exhaustive set of prongs are mandatory; a prong body is either a single expression or a block.
```flash
switch state {
.ok => log("all good"),
.retry, .pending => log("waiting"), // a multi-pattern prong
else => log("failed"),
}
```
### Inclusive Ranges (`...`)
Inside a prong, `lo...hi` matches an **inclusive** range — note the three dots, distinct from the two-dot slice bound. Like `if`, `switch` is also an expression, so every prong can yield a result value:
```flash
grade := switch score {
90...100 => 'A',
80...89 => 'B',
else => 'F',
}
```
### Payload Capture
When the subject is a tagged union, a `|value|` capture after `=>` binds the active variant's payload:
```flash
switch msg {
.text => |s| print(s),
.code => |n| print_num(n),
else => {},
}
```
---
## 5. Labeled Loops
A label on a `while` or `for` is a `break` / `continue` target, so an inner loop can leave or restart an **outer** one directly:
```flash
outer: for row in grid {
for cell in row {
if cell == target {
break :outer // leaves the labeled loop
}
if cell == 0 {
continue :outer // restarts the outer loop's next turn
}
}
}
```
The same label grammar already names blocks (`blk: { … break :blk v }`); loops and blocks share it. The label precedes `inline` (`outer: inline while …`), and the compiler resolves every labeled `break` / `continue` lexically — an unknown label, a `continue` aimed at a block label, or a label nothing targets each get a targeted diagnostic. A loop's label is not visible from its `else` arm.
---
## 6. Pointer Captures (`|*x|`)
By default a capture binds a **copy** of the element or payload. Prefixing the capture with `*` binds a *pointer* instead, so the body can write in place:
```flash
var arr [4]u8 = .{1, 2, 3, 4}
// Zero the array in place: p is a pointer to each element
for *p in &arr {
p.* = 0
}
```
The same `*` works on the `if` / `while` payload captures (`if opt |*x|`, `while it.next() |*v|`) and on switch prongs (`.variant => |*pay|`) — everywhere a pointer capture is meaningful. The `*` rides the element capture only: an index capture stays a value, a `*_` discard is rejected (there is no address worth taking), and error captures (`catch |e|`, `else |e|`) bind by value. The pointer itself is still an immutable capture — `p = q` is rejected; `p.* = v` is the point.