Unconditional loops are unconditionally awesome

Here’s a thing I don’t see appreciated enough about Rust: loop. I know I don’t think about it all that much, but pretty much every time I use it I feel a bit of satisfaction.

loop is just an unconditional loop: it loops forever, or until you write break, or return.

Most languages don’t have it.

Instead, loop constructs usually have some kind of termination condition, your while and for loops. Apparently Sather has an unconditional loop keyword like Rust. I only know this because a programming language historian mentioned it on the bug tracker.

Why do I love loop?

Because it frees me from thinking about how to end a loop before I’ve even started writing it.

Many problems are easy to recognize as ones that require a looping solution: I quickly realize, “I have to do something multiple times”.

Sometimes that loop just involves iterating over a container of things, one at a time. That’s easy to recognize as a for thing in things { } situation. Other loops have more complex conditions though. Often when I’m solving a looping problem, I will know one or some of the steps I intend to do in a loop, but will not envision the complete solution ahead of time.

So I just write

loop {
}

and start coding what I do know needs to happen, and work from there.

For some reason this feels great.

Once I have solved my looping problem, there will be a break or return somewhere in there, or multiple breaks and/or returns. Maybe it makes sense to convert it into a while loop, maybe not. But there’s not a great need for while when you’ve got loop, break, and return. Do readers really need to know the loop termination condition before reading what happens in the loop? Or, in languages with do { } while (…) loops, after the very end of the loop? I don’t know, but writing the termination condition naturally wherever it “wants” to live in the loop seems reasonable to me. One does though need to be considerate of their readers by keeping the loop body a readable length.

Anecdotally, a small project I’m working on right now contains 2 instances of loop, 2 of while let, and 8 of for … in; no standard while loops. And I think the loop loops read better than if I had tried to convert them to while loops.

Here’s one example:

let (tx, rx) = async_channel::unbounded();
let handle = thread::spawn(move || {
    let mut context = FsThreadContext::new();
    loop {
        let msg = block_on(rx.recv()).expect("recv");
        match msg {
            Message::Run(f) => {
                f(&mut context);
            },
            Message::Shutdown(rsp_tx) => {
                context.shutdown();
                rsp_tx.send(()).expect("send");
                break;
            }
        }
    }
});

Here’s the other:

let mut header = String::new();
let mut line = String::new();

loop {
    line.truncate(0);
    io.read_line(&mut line)?;

    if line.is_empty() {
        return Err(anyhow!("broken frame header"));
    }

    let maybe_body_marker = &line[..line.len() - 1];
    if maybe_body_marker == FRAME_BODY_MARKER {
        break;
    }

    header.push_str(&line);
}

It’s common in C-like languages to write an unconditional loop with while (true) { }.

I’m steeped enough in Rust that I don’t know if writing while (true) { } brings others the same satisfaction as I get from loop { }, but I suspect not: it looks and feels just like a tiny bit of a hack. And if I go into writing a loop by first writing while … then I am immediately presented with the question, “while what?”, and sometimes I just don’t want to think about that yet.

Besides feeling good, there is a technical reason that Rust has loop: it helps analyze control flow. With it, the compiler can trivially know that any code after the loop is unreachable. In Rust at least this is important for type checking. This kind of analysis is done in other languages, but by my recollection they sometimes actually special case while (true) { } for this purpose.

Here’s an example of the differences in how Rust treats loop vs. while true on the playground. Run it and check out the warning the compiler issues; try to alter the example as suggested in the comments.

Bonus Rust trivia: did you know that loop is an expression, with the same type as its break statements, and the result of loop can be assigned to a value?

I did not!

The issue on the bug tracker appears to be the only remains of the design discussion around loop in Rust, though it is insightful to the designers’ original thinking. The meeting minutes where it was approved just say there was consensus to add it.

loop {
    println!("Unconditional loops are unconditionally awesome");
}