Statements
Partly written. §1 to §5 were written with milestones M5 to M8a of the Lyric 5 plan and the catch-up block N (2026-10-07), §6 and the rules on bindings' types and on a missing return type with M17, spec-first: each rule lands here with its conformance case before or with its implementation. Source of the decisions: 08 Syntax.
Scope
Bindings, blocks, if, while, loop, for over Iterable (with try for throwing iterators), labels, break/continue/return, defer.
1. loop and the jumps
loop { … }runs its block again and again; only abreakleaves it (05 E11, 08 S3). At the start of a statement it is the statement, with no;after its block; anywhere else it is an expression —let found = loop { … };,return loop { … };.loopis a word only before its{: elsewhere it is a name like any other, and before a{it is always the loop.- A loop is worth what its breaks give (05 E11, 08 S4):
break value;leaves it with the value, and the loop's type is the values' types unified, or the type its position expects, each value checked against it —let n: ?int = loop { break null; };. A loop whose breaks give no value is worth nothing (void); a loop nobreakleaves is worthnever(06 §9.2) — a function whose body ends in one needs noreturn, and the loop fits where any value is wanted. The breaks of one loop give a value all or none (LYR-SEM0149). A value leaves only aloop:while,doandforalso end when their condition or their elements run out, and there would be no value then (LYR-SEM0148). - Labels:
outer: loop { … },outer: while (…) { … },outer: do { … } while (…);andouter: for (…) { … }name the loop, andbreak outer;,break outer value;andcontinue outer;leave or continue it from a loop nested in it. Afterbreak, a name is the label when a loop around thebreakcarries it, and the beginning of the value otherwise:break x;givesxunless a loop around is labeledx, andbreak (x);givesxwhatever it names. A jump to a label no loop around carries is refused (LYR-SEM0101), as is a label repeating one around it — a jump to it would be ambiguous (LYR-SEM0102); a label nothing jumps to is warned (LYR-SEM0103). Labels share nothing with the names of values. - A
breakorcontinueleaves or continues a loop of its own function: outside every loop it is refused (LYR-SEM0147). A lambda's body is a function of its own — the loops around the lambda are out of its jumps' reach, and their labels name nothing in it. A lambda an inline function's call sets in place is none (03 §8.5): its jumps reach the loops around it, and the inline body's loops are out of their reach. - Definite assignment follows the jumps. A branch of an
ifor an arm of amatchthat does not complete — it returns, throws, breaks, continues or givesnever— adds nothing to what follows it: the continuation follows the branches that complete. Aloopruns its block at least once and is left only by its breaks: a variable is assigned after it when it is assigned at everybreakthat leaves it. Ado … while (c)runs its block at least once, reachescfrom the end of the block and from everycontinue, and is left wherecis false and by everybreak. Awhileand aformay not run their block at all: what is assigned after them is what was assigned before. A read of a variable that is not assigned on every way to it is refused (LYR-SEM0018). - The end of a body follows the jumps too. A function that returns a value ends every path
in a
return, athrowor an expression worthnever(06 §9); a body that can run off its end is refused (LYR-SEM0017). A loop ends a path only when nothing leaves it: awhile (true)or ado … while (true)with nobreakof it, and adoloop whose block ends every path so and holds nobreakand nocontinueof the loop — abreakis a way past what follows it, and acontinuereaches the condition, which may end the loop. A jump is its loop's wherever it stands in the loop's block: in a nested statement, in theelseof alet … else, in an arm of amatchexpression or a clause of atryexpression that is part of a larger expression — and, when it names the loop's label, in a loop nested in it, aloopworthneverincluded. Only a lambda's body is out of reach (rule 4). A statement no path reaches is warned about (LYR-SEM0073); the statement behind a loop a jump leaves is reached. - A function written without a return type,
fn tick() { … }, returns nothing, as one written: voiddoes — the type is never inferred from the body (03 T8): its end andreturn;leave it, andreturn v;in it is refused (LYR-SEM0001).
2. for
for (x in e) { … }walks anIterable(design 10 B6 I2):e.iter()gives the iterator, once, and the loop calls itsnext()before every pass, running the block withxbound to the value untilnext()answersnull. Anything else is refused (LYR-SEM0007). Astringis noIterable: its characters and its lines are two walks,s.chars()ands.lines(), andfor (c in s)is refused (LYR-SEM0232, with.chars()as its fix). AnIteratoris iterable as itself (12 §Iteration), itsiter()the iterator as a value: a loop over a struct iterator walks a copy and leaves the variable where it was; over a class iterator it walks the object, and a second loop goes on where the first stopped.xis the iterator'sItem; a pattern takes it apart,for ((k, v) in …), where it cannot fail (LYR-SEM0098). AnItemmay itself be optional:next()then answers??T(03 §3.1), and anullof the inner level is a value the loop runs for. Two loops over oneIterablevalue each take an iterator of their own —iter()is called per loop.- A range written in the head is the counted loop (03 §7 rule 2); a range held in a binding is an iterator like any other.
- A loop over a binding — a local or a parameter — that an earlier loop walked warns
(
LYR-SEM0162, design 10 B6 I2) where the binding'siter()gives the iterator itself, the iterator may be an object other bindings reach — a class, or a type parameter or associated type a class may answer —, and the earlier loop stands before it in the same block or an enclosing one with no assignment to the binding between: the second loop goes on where the first stopped (rule 1). A struct iterator is walked as a copy and warns not, nor does anIterablewhoseiter()makes a new iterator, nor a loop after one in a branch that may not have run. - Over an array, a view or an inline array the loop is the index loop (design 10 B6
I10): the sequence is evaluated once — an inline array copied, as a binding copies it — and
the element at each index below its length is read at its pass, so a write to a later
element of an array or a view is seen, and a new array assigned to the variable is not. No
iterator stands between: an element that is itself optional walks. An array and a view are
Iterablebesides, throughSliceIter<T>(12 §Iteration), for what takes anIterable; an inline array is not, as a view of a local one is refused (03 §5.3 rule 4). - An iterator that may throw — its
Erroris notnever(design 10 B6 I5) — makes the loop's calls throw sites at its head,iter()once andnext()at every pass: the head is marked,for (line in try lines), thetrycovering the source and every pull (LYR-SEM0138without it) — in atryblock the block is the mark, as for every call in it, and one at the head is a second (06 §3 rule 2) — and what they throw is covered as a call's is, by atryblock around the loop or by the function's set. Through a constraint theErroris open: a loop overA :: [Iterable]throwsA.Iter.Error, which the function declares, and a call where it isneverthrows nothing. A mark over a loop that cannot throw is warned about (LYR-SEM0139). Only the plain mark stands in a head (LYR-SEM0163):try?,try!and the catching form would take the head's value and leave the pulls outside. - A loop over an iterator that is
Closeable— by its type, or by its constraints — closes it on every way out but a panic (design 10 B6 I6, 05 E7 R7), as ausingbinding closes what it binds: at its end, atbreak, atreturn, and when something in it throws;continuestays inside. Theclose()is a call of the loop's, marked by its head'strywhere it may throw (rule 6). Where the iterator's type names a type parameter whose constraints do not sayCloseable, the instance decides (the review's M8a-4): each monomorphized loop closes where the concrete iterator isCloseable, and does nothing where it is not. That close throws what the loop'snext()throws — the iterator'sError, which is all a Closeable iterator'sclose()may throw (12 §Iteration,LYR-SEM0174) —, so the head'stryand the clause that covers the pulls cover it.
3. Value blocks
- A value block is a block that is worth a value,
{ …; v }: statements, and last a tail — written without a;. It stands where the grammar puts one and nowhere else (08 Y4): as the body of a lambda, as a block arm of amatch, as a clause of atryexpression (06 §6), as a branch of anifexpression and as the right of??(08 §2). A brace anywhere else in an expression is an initializer's or a trailing lambda's: there is no block expression as such. - The tail is an expression — or an
ifwith itselse, amatchor aloopstanding last: there the three are worth what they are worth as expressions (the tail rule). Before the end of the block, and in every block that is no value block — a function's body, a loop's, the branches of anifstatement — they are statements, and a value without;in a statement block is refused (LYR-PAR0016). Anifwithout a finalelseis a statement wherever it stands, and so is one whose condition binds,if (let …). The form without braces,if (c) a else b, is an expression everywhere. - A value block is worth its tail's value. Without a tail it gives no value: it is worth
neverwhere every path through it leaves — byreturn,throw,break,continueor an expression worthnever(06 §9) — and nothing where it runs to its end. A block worth nothing stands where nobody takes a value; where the position wants one, or beside a branch or an arm that gives one, it is refused (LYR-SEM0033, or the branches do not unify,LYR-SEM0016). - A value block is a scope: its bindings end with it, and its
defers run when it ends — after the tail's value is taken. - A lambda's block is a value block in each of its forms:
(x) => { … },x => { … }, a trailing{ x => … }, and a trailing block that holds statements,{ let d = it * 2; d + 1 }. Its tail is the lambda's result, asreturn tail;at its end would be; a lambda nobody takes a value from may end in a tail worth nothing.
4. Bindings
letandvarbind a name in the scope of the block they stand in, from their statement to the end of the block; aletkeeps its value, avarmay be assigned. A localfnbinds its name likewise (03 §8.6).- A name is bound once in a scope (decided 2026-10-07): a second
letorvarof a name in the same block is refused (LYR-SEM0176). A parameter counts as bound in the body's own block, as in C#, Java and Go: aletof its name there is refused too — a lambda's parameter likewise. A block further in may bind the name again; its binding hides the outer one until that block ends._binds nothing (09 §1 rule 1):let _ = e;keeps a value nobody reads, as often in a block as it is written. - A binding takes its type from what it writes,
var n: int;, or from its initializer,let n = 1;(03 T8). One with neither,var x;, and one whose initializer fixes no type on its own —null,[]— are refused (LYR-SEM0010):let y: ?string = null;is the form. A binding with a type and no value is assigned before it is read (§1 rule 5).
5. if, while and do
if (c) { … }, withelse { … }orelse if (c) { … }after it:cis abool(LYR-SEM0004). The body of anif, anelse, awhile, adoand aforis a block, braced even around one statement —if (c) f();is refused (LYR-PAR0017, 08 Y5 S1). Anifas a value is 08 §2's;if (let p = e)andlet p = e else { … };take a pattern (09).while (c) { … }testscbefore every round;do { … } while (c);after every round, so its block runs at least once, and a;ends it. Acontinuein adogoes to the test (§1).
6. defer
deferB — a block, or an expression and its;— registers B in the scope it stands in, when the statement is reached; one not reached registers nothing. B runs when the scope is left, on every way out: its end, areturn, abreakorcontinuethat leaves it, and an error (06 §5) — not a panic (06 §9).- A scope's
defers run last registered first, in one list with itsusings (06 §7), and an inner scope's before an outer one's. - A loop's block is a scope per round: a
deferin it runs at the end of each round, before the next begins, and on thebreakorcontinuethat ends the round. - A
return's value is evaluated before thedefers run, as a value block's tail is (§3 rule 4):defer d(); return f();callsf, thend. - A
deferbody does not jump out of itself (08 S6): areturnin it, and abreakorcontinuethat goes to a loop around thedefer, are refused (LYR-SEM0236) — the body runs while its scope is being left, and a second way out would begin inside the first. A loop in the body keeps its own jumps, and a lambda there itsreturn. Athrowleaves it as rule 1 of 06 §5 and rule 5 there say.