ADR-070: There Is No Unconditional Loop Keyword — while true Is the Unconditional Loop

Status: Superseded by ADR-276 — its D1-D3 are D1, D2 and D6 there; D4 is not carried (a deferral, answered by ADR-071) Date: September 14, 2026 Target Version: Nikaia 0.0.8 Supersedes: — nothing. loop has been on the roadmap since the roadmap was written and was never in the specification, so this closes a question rather than reversing an answer Related: ADR-051 (every keyword is a reserved word, and the free moment to reserve one is before programs exist), ADR-023 D8 and ADR-025 D1 (what a loop does when a step fails), Part I 3.3


1. Context

Part I 3.3 has while and for and no third form. loop { … } has been on project_status_and_roadmap.md since it was written, is not in the specification, and the compiler having no rule for it is therefore correct rather than a gap. What a program writes today is while true { … }.

The evidence that decided it, stated accurately. Go is the language usually named here, and what it shows is one step to the side of what it is quoted for. Go has no while at all: for { … } is Go’s unconditional loop, for cond { … } its conditional one, for … range its iterating one. So Go did not do without an unconditional loop — it did without a second keyword for it, by letting one word carry all three forms.

That is the lesson, and it is the lesson this record applies. Nikaia already has the unconditional loop; it is spelled while true. The question was only whether it deserves a word of its own, and Go’s answer is no.

2. Decisions

D1 — while true { … } is the unconditional loop, and there is no second spelling for it.

One fewer keyword, and a reader has the whole of control flow on one page. A keyword is the most expensive thing a language adds and the hardest thing it removes — ADR-051 made every one of them a reserved word, which is what makes the addition cost more than the construct.

D2 — Rust’s argument for a loop keyword does not apply here, because there is no break.

Rust’s loop earns its word twice over: break value makes it an expression, and the checker knows loop { } with no break diverges, so it has type ! and nothing after it needs to exist.

Neither half reaches Nikaia today. break and continue are not in the grammar, not in the parser, and not among the 29 reserved words — a loop is left by its condition going false or by return leaving the whole function. So the expression argument is empty: there is no value for a break to carry.

The day that changes is the day to reopen this, and the reason is worth stating now rather than rediscovering: while true { … } whose break hands back a value reads as a lie, because the head says the loop does not end and the body says what it ends with.

D3 — The divergence half is a checker improvement, not a keyword.

Run rather than argued. This is refused today:

fn forever() -> i32 {
    while true {
        let x = 1
    }
}
error[NK1104]: this function hands back `()`, and it declares `i32`

so a function that genuinely never returns — an accept loop, an event loop, a supervisor — has to end with a return that cannot be reached. That is the one real cost of D1, and it does not need a keyword to fix: a while whose condition is the literal true cannot be left except by return, precisely because D2’s break does not exist, so everything after it is unreachable and the checker may say so. It is on #183 as work rather than here as a question.

D4 — Whether to reserve the word loop is a separate call, and this record does not make it.

ADR-051’s rule — reserving a word costs nothing before programs exist and breaks them afterwards, so the free moment is now — applies to loop, break and continue alike, and applies more strongly to the two that D2 says are likely to be wanted. Reserving is not adding: it keeps a door open at the price of a name nobody may use. That is a decision about the reserved list and not about control flow, and putting it here would smuggle a language change into a record that says no to one.

3. Consequences

Part I 3.3 says the absence is a decision. That is the whole cost of D1 and the point of having asked: an absence a reader can see the reason for is not an omission.

No program changes. No .nika file in the tree writes while true, so the form nobody uses did not need a second spelling — which is also why this was cheap to decide and would not have been cheap to decide later.

One item joins the work list (D3), and one question is named but not answered (D4).

4. What is built

The specification. Part I 3.3 carries the sentence and names this record.

Nothing in the compiler, which already had no rule for loop and needed none added. D3’s unreachability is on #183; it is an improvement to a diagnostic and not a condition of this decision.

This site is open source. Improve this page.