Skip to content

← Ground Rules: Values, Types, Control Flow step 15 of 24

Easy Primitives

loop, while, for, and break with a value

Count the Collatz steps needed to reach 1.

pub fn collatz_steps(start: u64) -> u32

Repeatedly: if the number is even, halve it; otherwise triple it and add one. Count how many of those operations it takes to arrive at 1. 1 takes 0 steps, 6 takes 8, and 27 takes 111.

The starter does not compile, and its error is the point of the exercise.

Three loops, and when each is right

loop — repeat until something inside says stop. Use it when the exit condition is not a simple test at the top, which is exactly this problem.

while cond — test at the top, repeat while true. Use it when there is a single, cheap condition.

for item in iterable — walk a collection or a range. This is the one you should reach for by default, and Rust makes it the only pleasant way to walk a collection, which is not an accident.

loop gets a privilege the other two do not have: because it can only be left by break (or return, or a panic), the compiler knows it either runs forever or exits through a break you wrote — so loop can produce a value.

let answer = loop {
    if done {
        break 42;        // this is the loop's value
    }
    // …
};

break; with nothing after it breaks with (). break value; breaks with value. Both are legal; they just have different types. The starter breaks with nothing, so its loop is worth (), so the function — which promised u32 — gets E0308: mismatched types, expected u32, found ().

while and for cannot do this: they can end by their condition going false, and there would be no value to produce in that case. Their type is always ().

The gate’s best teaching moment in this track

Write this and clippy stops you:

for i in 0..v.len() {
    total += v[i];
}

needless_range_loop is on by default and it will tell you to write for item in v instead — or v.iter().enumerate() if you genuinely need the index. Its argument is not aesthetic. The index version does a bounds check on every access, it lets you index the wrong collection by mistake, and it puts a number between you and the thing you actually wanted. Iterating directly cannot be off by one.

Be aware of a real gap in the lint: a hand-rolled while i < v.len() index loop does not trip it. The lint recognises the for … in 0..len shape, not the idea. So the gate will not catch every version of this mistake, and you should learn the habit rather than relying on the tool.

is_multiple_of again

n % 2 == 0 on a u64 trips manual_is_multiple_of (stabilised as a method in 1.87, linted since). Write n.is_multiple_of(2).

A note on the numbers

start is a u64 and the intermediate values genuinely need the room: the Collatz sequence for 27 climbs to 9232 on its way down. There is no known input for which this loop does not terminate, and there is also no proof that one does not exist — the Collatz conjecture is open. The test inputs are all small and verified.

Loading visualization…