Chapter 5 — Control Flow

This is the chapter where programs start making decisions and going round in circles, which is most of what programs do.

if / else

fn classify(n: int) -> int {
    if n < 0 {
        return -1
    } else if n == 0 {
        return 0
    } else {
        return 1
    }
}

Braces are always required — there’s no “one-liner without braces” form to trip over. The condition must be a bool (we covered why in Chapter 3: no truthiness, ever). else if chains as far as you need.

loop, break, continue

Ingle’s one looping primitive is loop, which loops forever until you break out of it. continue skips to the next turn.

fn sum_to(n: int) -> int {
    var i = 0
    var total = 0
    loop {
        if i >= n { break }
        total = total + i
        i = i + 1
    }
    return total
}

break and continue are only legal inside a loop; using them anywhere else is a compile error. You might be looking at that loop and thinking “where’s while? where’s the C-style for?” — and the answer is the next chapter, which has a much nicer for. For counting and walking over data you’ll reach for that; loop is for the genuinely open-ended cases.

Blocks and scope

Every { } is a scope. A let or var declared inside one lives until that block’s closing brace and no longer — if, else, loop, and even a bare { } you write yourself all create a fresh scope. And, as we saw with bindings, an inner name may shadow an outer one:

fn main() -> int {
    let x = 1
    {
        let x = 99      // a different x, just for this block
        println("{x}")  // 99
    }
    println("{x}")      // 1 — the outer x was never touched
    return x
}

This is exactly the block scoping you know from C-family languages, with no surprises. The shadowing is the one thing C# doesn’t let you do (it forbids a local shadowing another local); Ingle permits it, because it’s genuinely useful for refining a value step by step, and the strict immutability of let keeps it from being confusing.