Scope and ownership
링크This page owns block execution, conditional and iterative control flow, labels, explicit transfers, and deferred cleanup.
Pattern syntax belongs to Pattern matching. Range operators belong to Expressions and operators. Function return inference belongs to Functions and generics. Collection construction and comprehensions belong to Collections and comprehensions. Concurrent timeout behavior and CLI lifecycle are outside this page.
Control-flow syntax
링크Block = "{" Statement* Expression? "}" ;IfExpression = "if" IfHead Block ("else" (IfExpression | Block))? ;IfHead = ControlOperand | "let" Pattern "=" ControlOperand ;WhileStatement = "while" WhileHead Block ;WhileHead = ControlOperand | "let" Pattern "=" ControlOperand ;ForExpression = "for" Pattern "in" ControlOperand Block ;LoopExpression = "loop" Label? Block ;BreakStatement = "break" Label? Expression? ;ContinueStatement = "continue" Label? ;ReturnStatement = "return" Expression? ;DeferStatement = "defer" Block ;ControlOperand = "(" Expression ")" ;
This is a manually transcribed source-syntax excerpt. Statement boundaries belong to Lexical structure. Patterns belong to Pattern matching. Call arguments belong to Functions and generics.
A label starts with an apostrophe, as in loop 'outer { ., break 'outer 7, or continue 'outer. The label follows loop, break, or continue.
The iterator in for item in (expression) { . can be a range. In for n in (0., by 2 is part of the range expression. Range construction and bounds belong to Expressions and operators.
Blocks and final values
링크Block execution and final value
On normal completion, a block executes its statements in source order. It then evaluates its optional final expression and produces that value. Trailing line breaks, blank lines, and comments preserve that final value. Without a final expression, it produces (). Write let _ to evaluate an expression once without retaining its value. A final discard binding leaves the block without a tail, so the block produces (). A statement separator never consumes a value.
이유
A block has one final-value rule. Formatting the closing delimiter, adding blank lines, or ending a line comment cannot consume that value. A discard binding names the intention to run an expression once and finish with unit. Statement and control-flow effect contexts retain their existing result rules.
If, else, and if let
링크Choosing an if branch
An if condition has type bool. A true condition selects the then branch. A false condition selects the else arm when present and otherwise skips the then branch. An else arm can be a block or another if expression. The unselected branch is not evaluated. An if expression with an else arm produces the selected branch's value. Without an else arm, the expression always has the unit type and produces () on normal completion, including in a value position. A selected then branch runs for its effects and its value is discarded.
이유
Boolean conditions avoid implicit truth conversions. An if without else has the same unit result in statement and value positions.
Conditional pattern binding
if let evaluates its scrutinee once. A successful pattern selects the then block and makes its bindings available in that branch. Failure selects the else arm when present. The result follows the if expression result rules. Without an else arm, the expression has the unit type and produces () on normal completion whether the pattern matches or fails. A selected then block discards its value when the else arm is absent.
이유
Evaluating the scrutinee once avoids repeating its effects. Keeping bindings in the successful branch prevents access to values from a failed match.
While, while let, and for
링크Testing before each while iteration
A while loop tests its bool condition before each body execution. A true condition runs the body, and a false condition finishes the loop. A continue targeting this loop resumes at the next condition test. A break targeting it transfers control after the loop.
이유
Testing before the body lets an initially false condition skip the loop. Testing again after continue preserves the condition for every iteration.
Matching before each while let iteration
while let evaluates its scrutinee before each body execution. A successful pattern runs the body with its bindings. Pattern failure finishes the loop. A continue targeting this loop resumes at the next scrutinee evaluation.
이유
A fresh match checks each iteration against the current value. Its bindings describe the value that admitted that iteration.
Statement and value uses of for
A for expression evaluates its iterator expression once, visits elements in iterator order, and uses its pattern to bind each element.
Every for runs its body for effects, discards each completed body value, and produces (). This type and its break and continue targets stay the same in a statement, parentheses, initializer, argument, or function tail. An empty iterator also produces ().
Construct an array with [for item in (values) => expression]. Set and map comprehensions use their corresponding collection delimiters. A for expression does not collect body results. Requiring an array where a for produces () is an ordinary type mismatch.
Iterable admission and element-pattern failure are specified below.
이유
A for expression performs iteration for effects, while a comprehension constructs a collection. Giving every for the same unit result prevents parentheses, binding, and argument position from changing its operation or loop targets. The existing collection delimiters make collection construction explicit. Collecting values with an early stop has a different control-flow intent. Use an accumulator and break for that case. A comprehension expresses collection that runs to completion.
Iterable admission and element-pattern failure
For expressions admit Array, Set, Range and bind their element type in every context. Other resolved concrete types are rejected with TPZ5001. Iterate map keys or entries explicitly. Convert a string with scalars() or graphemes() to choose the text unit.
A generic, foreign, or unresolved inferred type defers the element decision. At runtime the value must still be an array, set, or range. Array and set iteration traverse the collection value at entry under collection value semantics.
A refutable element pattern must match each visited element. A miss raises a runtime type fault before the body executes for that element.
이유
The collection value at entry keeps an iteration stable when the source variable changes. A failed element pattern faults before the body can use missing bindings.
Loop labels, break, continue, and return
링크Selecting a break or continue target
break, continue can include a target label. Lookup stays inside the current function and respects concurrent arm boundaries. An unlabeled transfer selects the innermost loop frame. A labeled transfer selects the nearest enclosing loop with that label. Targets include while loops, for expressions in every context, and loop expressions.
A transfer without an enclosing loop is rejected during checking with TPZ5001. An unknown loop label is rejected during checking with TPZ5720.
이유
Function boundaries prevent loop transfers from escaping into a caller. A label makes the intended enclosing loop visible when loops are nested.
Leaving a loop with a value
A loop label appears after loop. A break targeting the loop completes it with the evaluated break operand, or () when the operand is absent. A continue targeting it repeats the body. Without a contextual result type, break types must be assignable in at least one direction after widening. Disagreement is rejected during checking with TPZ5721.
이유
The break operand supplies the result at the exit point. Checking its type against other exits gives the loop a usable result type.
Returning from a function
A return is a statement and requires a function context. Its target cannot cross a concurrent arm boundary. Its value expression is evaluated before cleanup. A return without a value supplies (). Cleanup for crossed scopes runs before control transfers to the enclosing function's caller. A return outside a function is rejected during checking with TPZ5001.
A return inside a lambda ends that lambda invocation. The same callable boundary determines lambda and callback propagation.
이유
Evaluating the return value before cleanup preserves the selected result when cleanup changes state. The enclosing function fixes the transfer destination.
Break values in while and for loops
A break targeting a while loop or for expression evaluates its operand once before cleanup. The operand result is discarded. The target loop completes with (). A break without an operand follows the same cleanup and exit order. These rules also apply when the for expression is grouped or bound to a name.
이유
Evaluating the operand before cleanup preserves its effects. All for expressions and while loops complete with unit, so grouping or binding does not change the meaning of a break. Cleanup order remains consistent with return and value-producing break.
Deferred cleanup
링크Registering a deferred block
A defer operand must be a block. A call without a block and other operands are rejected during parsing with TPZ2004. Write defer { cleanup() } to defer a call. The block is registered when execution reaches the defer statement and runs during cleanup.
이유
A block gives a single call and a sequence of cleanup steps the same syntax. Ordinary block rules determine how the action runs. A call form would evaluate its arguments when cleanup is registered, although cleanup happens when the scope exits. A block keeps evaluation at cleanup time. Use let to capture a value earlier when that is intended.
Cleanup order on normal completion and control transfer
A reached defer belongs to its enclosing block. Cleanup runs on normal block completion and when return, break, continue, or an Err propagated by ? leaves that scope. An Ok operand of ? continues without leaving the scope or running its cleanup.
Actions run in reverse registration order within each scope. Crossed scopes are cleaned up from innermost to outermost. The block or transfer result is evaluated before cleanup. An ordinary runtime fault follows the fault cleanup rule.
Deadline abandonment follows abandoned arm cleanup. Remaining language cleanup is skipped and runtime handle reclamation belongs to the group.
이유
An Err propagated by ? leaves scopes just as an explicit return does. A common cleanup order keeps resources available until their dependents finish.
Runtime faults skip remaining cleanup
An ordinary runtime fault is an uncatchable termination. Remaining defer blocks and using cleanup do not run. Normal completion and structured exits retain the defer order.
Normal completion and structured exits follow the defer order.
Deadline abandonment follows abandoned arm cleanup. Remaining language cleanup is skipped and runtime handle reclamation belongs to the group.
이유
A single fault outcome makes output reproducible. Skipping cleanup also prevents a second cleanup fault after termination.
Complete examples and expected results
링크Each example below is a complete file. Its expected result is specified before execution.
A block value and a selected branch
링크import std.iofunction choose() -> int {let value = {let base = 4base + 3}if (value > 5) {value * 2} else {0}}io.writeLine("{choose()}")
14
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
14
관측한 단언 · 통과 · 5.21.3
종료 코드 0.
14
표준 오류 출력 없음.
- 통과 · expected 0; observed 0
- 통과 · stdout matches the authored expectation
- 통과 · stderr matches the authored expectation
- 통과 · stdout matches the authored expectation
Expected stdout is 14 followed by a newline.
Using if let and while let
링크import std.iolet mut next: Option<int> = Some(3)if let Some(value) = (next) {io.writeLine("ready: {value}")} else {io.writeLine("empty")}while let Some(value) = (next) {io.writeLine("{value}")next = None()}io.writeLine("done")
ready: 3
3
done
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
ready: 33done
관측한 단언 · 통과 · 5.21.3
종료 코드 0.
ready: 33done
표준 오류 출력 없음.
- 통과 · expected 0; observed 0
- 통과 · stdout matches the authored expectation
- 통과 · stderr matches the authored expectation
- 통과 · stdout matches the authored expectation
The while-let body changes the next scrutinee to None(). The following test ends the loop.
Continue in while and break in for
링크import std.iolet mut tick = 0while (tick < 3) {tick += 1if (tick == 2) {continue}io.writeLine("{tick}")}for n in (0..<8 by 2) {if (n == 4) {break}io.writeLine("{n}")}io.writeLine("done")
1
3
0
2
done
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
1302done
관측한 단언 · 통과 · 5.21.3
종료 코드 0.
1302done
표준 오류 출력 없음.
- 통과 · expected 0; observed 0
- 통과 · stdout matches the authored expectation
- 통과 · stderr matches the authored expectation
- 통과 · stdout matches the authored expectation
- 통과 · stdout matches the authored expectation
Both loops are used for their effects. The range syntax and step are defined under Expressions and operators.
Breaking out of an enclosing labeled loop
링크import std.iolet result: int = loop 'outer {loop {break 'outer 7}}io.writeLine("{result}")
7
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
7
관측한 단언 · 통과 · 5.21.3
종료 코드 0.
7
표준 오류 출력 없음.
- 통과 · expected 0; observed 0
- 통과 · stdout matches the authored expectation
- 통과 · stderr matches the authored expectation
- 통과 · stdout matches the authored expectation
The label selects the enclosing loop. The break supplies its result.
Running deferred actions before returning
링크import std.iofunction result() -> int {io.writeLine("value")7}function finish() -> int {defer { io.writeLine("first") }defer {io.writeLine("second")}io.writeLine("body")return result()}io.writeLine("{finish()}")
body
value
second
first
7
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
bodyvaluesecondfirst7
The return operand runs before cleanup. Deferred blocks run in reverse registration order.
Cleaning up scopes during error propagation
링크import std.iofunction fail() -> Result<int, string> {io.writeLine("operand")Err("stop")}function finish() -> Result<int, string> {defer { io.writeLine("outer") }let value = {defer { io.writeLine("inner-first") }defer { io.writeLine("inner-second") }fail()?}io.writeLine("unreachable")Ok(value)}io.writeLine(match (finish()) {case Ok(value) => "value={value}"case Err(error) => error})
operand
inner-second
inner-first
outer
stop
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
operandinner-secondinner-firstouterstop
The operand runs once before cleanup. Error propagation runs inner defers in reverse order, then the outer defer, before returning the error.
Rejecting an invalid deferred expression
링크defer 5
기대 결과
The integer operand is rejected at the start of 5. Replace it with a block that performs cleanup.
Break operands run before cleanup in statement loops
링크import std.iofunction operand(label: string) -> int {io.writeLine(label)7}while (true) {defer { io.writeLine("while cleanup") }break operand("while value")}for item in ([1, 2]) {defer { io.writeLine("for cleanup") }break operand("for value")}io.writeLine("done")
while value
while cleanup
for value
for cleanup
done
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
while valuewhile cleanupfor valuefor cleanupdone
The expected result follows from current SELF source. Execution against public Topaz 5.21.3 is pending CI.
Break in a bound for preserves its unit type
링크let result: () = for item in ([1]) {break 7}
표준 출력 없음.기대 결과
기대 결과
검사가 프로그램을 받아들입니다. 종료 코드 0.
The explicit unit annotation also applies when the for is a binding initializer. The break operand does not become a collected element.
Arrays, sets, ranges, and matching element patterns
링크import std.iofor item in ([1, 2]) {io.writeLine("array {item}")}for item in (set { 3, 1, 3 }) {io.writeLine("set {item}")}for item in (2..4) {io.writeLine("range {item}")}for Some(item) in ([Some(7), Some(8)]) {io.writeLine("matched {item}")}
array 1
array 2
set 3
set 1
range 2
range 3
range 4
matched 7
matched 8
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
array 1array 2set 3set 1range 2range 3range 4matched 7matched 8
The expected result follows from current SELF source. Execution against public Topaz 5.21.3 is pending CI.
A string must be converted to scalar elements before iteration
링크for item in ("hi") {}
기대 결과
The expected result follows from current SELF source. Execution against public Topaz 5.21.3 is pending CI.
An omitted else discards values and preserves selected effects
링크import std.iolet taken = if (true) {io.writeLine("then")7}let skipped = if (false) {io.writeLine("unexpected")9}io.writeLine("{taken}")io.writeLine("{skipped}")
then
()
()
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
then()()
An omitted else cannot produce an int result
링크let value: int = if (true) { 7 }
표준 출력 없음.기대 결과
Rejecting a deferred call without a block
링크import std.iofunction finish() -> () {defer io.writeLine("cleanup")}finish()
기대 결과
Wrap the cleanup call in a defer block. The current SELF parser still accepts this removed form.
Running a deferred block at scope exit
링크import std.iofunction finish() -> () {defer { io.writeLine("cleanup") }io.writeLine("body")}finish()
body
cleanup
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
bodycleanup
The body prints before the registered cleanup block runs.
Returning through a match arm block
링크import std.iofunction choose(flag: bool) -> int {while (true) {let value = match (flag) {case true => { return 7 }case false => 3}return value}0}io.writeLine("{choose(true)}")io.writeLine("{choose(false)}")
7
3
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
73
The true arm returns from the function through the surrounding binding and loop. The false arm supplies a value to the ordinary return statement.
For results stay unit in grouped and bound positions
링크import std.iofunction items(label: string) -> Array<int> {io.writeLine(label)[1, 2]}let bound = for item in (items("bound")) {io.writeLine("{item}")item * 2}let grouped = (for item in (items("grouped")) {io.writeLine("{item}")item * 3})let emptyItems: Array<int> = []let empty = for item in (emptyItems) { item }io.writeLine("{bound} {grouped} {empty}")
bound
1
2
grouped
1
2
() () ()
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
bound12grouped12() () ()
The iterator calls and body output establish evaluation order. Binding, parentheses, and an empty iterator all retain the unit result.
Break and continue survive grouping and binding
링크import std.iofunction grouped() -> () {(for item in ([1, 2, 3, 4]) {if (item == 2) { continue }if (item == 4) { break }io.writeLine("grouped {item}")})}grouped()let result = for item in ([1, 2, 3, 4]) {if (item == 2) { continue }if (item == 4) { break }io.writeLine("bound {item}")}io.writeLine("{result}")
grouped 1
grouped 3
bound 1
bound 3
()
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
grouped 1grouped 3bound 1bound 3()
Both loops skip item 2 and stop before item 4. Grouping a for at a function tail and binding its result preserve the same loop targets.
For cannot supply an array result
링크let values: Array<int> = for item in ([1, 2]) { item * 2 }
표준 출력 없음.기대 결과
A for expression has type (). An array annotation therefore rejects this initializer. Use an array comprehension to collect the body values.
Collect values with an array comprehension
링크import std.iolet values: Array<int> = [for item in ([1, 2]) => item * 2]io.writeLine("{values[0]} {values[1]}")
2 4
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
2 4
The collection delimiters request array construction. The body supplies one element per visited input.
Trailing layout preserves values and discard evaluates once
링크import std.iofunction inlineTail() -> int { 7 }function linesTail() -> int {7}function commentedTail() -> int {7 // Trailing layout preserves this value.}function mark(value: int) -> int {io.writeLine("mark {value}")value}let discarded: () = {let _ = mark(9)}let bound: int = { 11 }io.writeLine("{inlineTail()}|{linesTail()}|{commentedTail()}|{bound}")io.writeLine("done")
mark 9
7|7|7|11
done
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
mark 97|7|7|11done
A unit context accepts successful and failed if let expressions without else
링크let takenInput: int = 7let skippedInput: int = 8let taken: () = if let 7 = (takenInput) { 9 }let skipped: () = if let 7 = (skippedInput) { "discarded" }
표준 출력 없음.기대 결과
기대 결과
검사가 프로그램을 받아들입니다. 종료 코드 0.
An if let without else discards branch values and preserves selected effects
링크import std.iolet takenInput: int = 7let skippedInput: int = 8let taken = if let 7 = (takenInput) {io.writeLine("then")9}let skipped = if let 7 = (skippedInput) {io.writeLine("unexpected")"discarded"}io.writeLine("{taken}")io.writeLine("{skipped}")
then
()
()
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
then()()
A runtime fault leaves defer output absent
링크import std.iofunction divide(divisor: int) -> int {defer { io.writeLine("cleanup") }io.writeLine("before fault")12 / divisor}io.writeLine("{divide(0)}")
before fault
기대 결과
The registered defer is not executed. The program prints before fault, then terminates at the division.
Normal completion runs the registered defer
링크import std.iofunction divide(divisor: int) -> int {defer { io.writeLine("cleanup") }io.writeLine("before result")12 / divisor}io.writeLine("{divide(2)}")
before result
cleanup
6
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
before resultcleanup6
Related rules and diagnostics
링크See Pattern matching for pattern forms and matching, Expressions and operators for range expressions, and Functions and generics for function declarations and return inference.