ajhahn.de
← Flash
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.