Scope and ownership
링크This page owns the concurrent forms, arm results, completion, failure, and timeout behavior.
Functions and generics owns calls. Blocks and control flow owns general control transfer. Bindings and scope owns captures and mutation. Failure and propagation owns language-level propagation. Thread and scheduler implementation details are outside this page.
The typed compilation path currently rejects timed groups as a whole. This applies to every timed group, including groups with no defer or using. Its contract and unsupported semantic-role path were inspected in source. No typed execution observation is attached here. The installed engine is assessed separately by the examples and the host handle reclamation limitation below.
Concurrent forms
링크Concurrent = Join | Timed ;Join = "concurrent" "{" ArmSequence? "}" ;Timed = "concurrent" "(" "timeout" ":" Expression ")""{" ArmSequence? "}" "else" Block ;Arm = Identifier ":" Expression ;DurationLiteral = DecimalInteger DurationUnit ;DurationUnit = "ms" | "s" | "m" ;
This grammar imports separators, expression grouping, blocks, and decimal integer spelling from their language chapters. Arm names the arm sequence.
Named arm expressions
Each concurrent arm has the form name: expression, where name is an identifier.
이유
Arm labels become record fields. The same name: value shape connects each computation to the field that will hold its result.
Concurrent groups may contain zero arms
A concurrent group may contain zero arms. An untimed empty group completes immediately with the zero-field structural record. Its type is written {} in type positions, and it is distinct from unit (). The ordinary empty expression block {} retains its unit result.
A timed empty group evaluates its Duration expression exactly once before the group, then follows the same deadline/completion arbitration as every timed group. A zero deadline selects the else block. The fallback must satisfy the same zero-field record result type. An untimed concurrent {} can supply that value.
이유
Each concurrent arm contributes one named result field. With no arms, the result is the zero-field structural record, and an untimed group is already complete. The matching empty structural type can be written explicitly so adding a type annotation does not change which result is expressible. Timed empty groups retain the existing deadline priority, including a zero deadline.
Timeout and fallback appear together
The concurrent(timeout: . form requires an else block. An else block requires the timeout form. Either missing partner is rejected with TPZ2005.
이유
A timed group can finish without a complete result record. A fallback supplies the value or control transfer for that path.
A timeout accepts a Duration expression
The timeout operand is an ordinary expression whose resolved type is Duration. Parentheses, bindings, calls, and conditionals obey the same expression and type rules. An int or float does not implicitly convert to Duration. An incompatible operand produces TPZ5001 during checking. Evaluate the operand exactly once, before starting its deadline or any arm. If that evaluation fails, propagate its original failure and location without starting an arm or evaluating the fallback. After it succeeds, start the deadline and apply the existing completion/deadline arbitration. A zero deadline wins even against immediately ready arms.
이유
A duration keeps its value and type when it is parenthesized, bound to a name, returned from a function, or selected by an expression. Checking that value against Duration gives timeout one ordinary expression rule. Evaluating it once before the group starts prevents duplicated effects and keeps computing a budget outside the budget it establishes.
Arms and result fields
링크Distinct arm names
Arm names in one concurrent expression must be distinct. A repeated arm name is rejected with TPZ5008.
이유
Each arm supplies one result field. Repeated names would make that field ambiguous or silently discard an arm value.
The result record type
When the arm result types are known, a concurrent expression has a structural record type. Each arm name becomes a field name. Each field type is the widened type inferred for its arm expression.
이유
Named fields preserve which computation produced each value regardless of completion order. Ordinary record access can then read the results.
Arm names, enclosing scope, and visible effects
Arms resolve enclosing names using the ordinary lexical rules. Names introduced inside an arm block stay in that block. Arm labels name result fields and do not introduce bindings. Start order, completion order, and interleaving of visible effects are unspecified. Read completed fields after the expression returns when output order matters.
Progress and effects when work is abandoned follow timed group progress.
이유
Lexical scope gives enclosing names their usual meaning inside an arm. Scheduling remains independent of source order so arm placement does not promise effect order.
Each arm has its own control boundary
Each concurrent arm is an independent control boundary. A return or postfix ? cannot target a function outside the arm. A break or continue cannot target an outer loop. Missing arm-local function or unlabelled loop targets are rejected during checking with TPZ5001. A loop label without a target inside the arm is rejected during checking with TPZ5720. Functions called by the arm and loops introduced inside it consume their own control transfers.
An arm ends the search for enclosing return and loop targets. TPZ5001 already diagnoses a return or failure propagation without a function context, and unlabelled loop control without a loop context. TPZ5720 already diagnoses a label without an enclosing loop target. These existing diagnostics apply after the arm boundary removes outer targets from that search.
이유
Each arm runs independently. Keeping control transfers inside its boundary avoids making sibling cancellation depend on a return or loop outside the arm.
Completion and failure
링크Collect completed values
When every arm finishes successfully before any applicable timeout, the concurrent expression produces a record containing the named arm values.
이유
A completed record must contain every declared field. Waiting for all arms prevents callers from receiving missing results.
Result values remain field values
An arm that completes with an ordinary Result value stores that value in its result field, preserving Ok(. or Err(.. The concurrent expression does not apply postfix propagation to that value.
이유
Result variants are ordinary values until a program handles or propagates them. Preserving each variant keeps concurrency from adding implicit propagation.
An arm execution failure leaves the expression
If an arm reports an execution failure before the timeout is reached, that failure leaves the concurrent expression without evaluating its fallback. The same failure transfer applies to the form without a timeout.
이유
The fallback handles an expired deadline. Using it for an arm fault would hide that failure and give the fallback two different meanings.
Evaluate the fallback after timeout
When the deadline is reached before all arms complete, or coincides with completion, unfinished arms are abandoned and collected result values are discarded. The concurrent expression then evaluates its else block.
Progress and effects when work is abandoned follow timed group progress.
The remaining defer blocks and using cleanup of an abandoned arm follow abandoned arm cleanup.
이유
The fallback replaces the group result as a whole. Discarding collected values prevents the returned fields from depending on scheduling.
Deadline priority and competing faults
Timeout takes priority when reaching the deadline coincides with arm completion, including a zero-millisecond timeout. The else block determines the result in that case. When arm faults occur simultaneously, arm declaration order gives no fault priority.
Progress and effects when work is abandoned follow timed group progress.
The remaining defer blocks and using cleanup of an abandoned arm follow abandoned arm cleanup.
이유
Giving the deadline priority makes a boundary case reproducible, including a zero timeout. Fault priority does not change when independent arms are reordered.
Timed group progress
A timed concurrent reaches its fallback after the deadline even when an arm cannot finish, including an infinite loop or deep recursion. A host call already in progress finishes without interruption and can delay group completion. An abandoned arm produces no effects after the group finishes. Effects that occurred before completion remain. A group without a timeout makes no fairness guarantee.
See timeout fallback, deadline races, and abandoned arm cleanup.
The remaining defer blocks and using cleanup of an abandoned arm follow abandoned arm cleanup.
이유
A timeout must let a group finish even when an arm does not. Host calls finish as one operation. Untimed fairness would impose scheduling work on every program.
Deadline abandonment skips language cleanup
A concurrent arm abandoned by a deadline does not execute its remaining defer blocks or using cleanup. Normal block completion and structured exits through return, break, continue, or an Err from ? run language cleanup. Abandonment follows the same cleanup rule as an ordinary runtime fault. Cleanup performed by an arm that completed before the deadline remains observable. Host resource handles follow runtime reclamation.
이유
Running cleanup after a deadline would execute more user code. Slow or nonterminating cleanup would break timed group progress. Running it would also require a separate cancellation path. Abandonment follows the runtime fault cleanup rule, giving both exits one cleanup behavior.
Runtime reclamation of abandoned arm handles
When the group finishes, the runtime reclaims host resource handles opened by an abandoned arm, such as File handles. This reclamation is performed by the runtime and does not execute the arm's remaining language cleanup.
이유
Runtime reclamation releases host resources without running user cleanup after the deadline. This preserves timed group progress.
Duration and fallback values
링크Duration units
이유
A shared literal definition keeps timeout expressions consistent with ordinary expressions.
Duration literal bounds
이유
Keeping literal bounds in one place prevents timeout syntax from acquiring a separate value range.
The fallback result type
When the arm result types are known, a value-producing fallback is checked against their record type. A fallback that terminates control flow is checked without that expected result type.
이유
Successful completion and the fallback feed the same expression. Matching their record types lets callers use the result without testing which path completed.
Examples and expected checks
링크Read both completed arm values
링크import std.iolet joined = concurrent {first: 2second: 3}io.writeLine("{joined.first}")io.writeLine("{joined.second}")
2
3
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
23
관측한 단언 · 통과 · 5.21.3
종료 코드 0.
23
표준 오류 출력 없음.
- 통과 · expected 0; observed 0
- 통과 · stdout matches the authored expectation
- 통과 · stderr matches the authored expectation
Keep an Err value in the result record
링크import std.iofunction completeError() -> Result<int, string> {Err("missing")}let joined = concurrent {value: completeError()count: 7}match (joined.value) {case Ok(_) => io.writeLine("unexpected")case Err(message) => io.writeLine(message)}io.writeLine("{joined.count}")
missing
7
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
missing7
관측한 단언 · 통과 · 5.21.3
종료 코드 0.
missing7
표준 오류 출력 없음.
- 통과 · expected 0; observed 0
- 통과 · stdout matches the authored expectation
- 통과 · stderr matches the authored expectation
Distinct arm names and result fields
링크let joined = concurrent {first: 1second: 2}let first: int = joined.firstlet second: int = joined.second
표준 출력 없음.기대 결과
기대 결과
검사가 프로그램을 받아들입니다. 종료 코드 0.
관측한 단언 · 통과 · 5.21.3
종료 코드 0.
표준 출력 없음.
표준 오류 출력 없음.
- 통과 · expected 0; observed 0
- 통과 · stdout matches the authored expectation
- 통과 · stderr matches the authored expectation
A fallback with the corresponding result fields
링크let joined = concurrent(timeout: 25ms) {count: 2 + 3label: "ready"} else {{ count: 0, label: "timeout" }}let count: int = joined.countlet label: string = joined.label
표준 출력 없음.기대 결과
기대 결과
검사가 프로그램을 받아들입니다. 종료 코드 0.
관측한 단언 · 통과 · 5.21.3
종료 코드 0.
표준 출력 없음.
표준 오류 출력 없음.
- 통과 · expected 0; observed 0
- 통과 · stdout matches the authored expectation
- 통과 · stderr matches the authored expectation
The three duration units
링크let milliseconds = concurrent(timeout: 1ms) { value: 1 } else { { value: 0 } }let seconds = concurrent(timeout: 1s) { value: 1 } else { { value: 0 } }let minutes = concurrent(timeout: 1m) { value: 1 } else { { value: 0 } }
표준 출력 없음.기대 결과
기대 결과
검사가 프로그램을 받아들입니다. 종료 코드 0.
관측한 단언 · 통과 · 5.21.3
종료 코드 0.
표준 출력 없음.
표준 오류 출력 없음.
- 통과 · expected 0; observed 0
- 통과 · stdout matches the authored expectation
- 통과 · stderr matches the authored expectation
Repeated name in one concurrent expression
링크let joined = concurrent {value: 1value: 2}
표준 출력 없음.error[TPZ5008]: duplicate concurrent arm `value`
--> example.tpz:3:5
|
3 | value: 2
| ^^^^^
example.tpz: 1 type diagnostic
기대 결과
관측한 단언 · 통과 · 5.21.3
종료 코드 1.
표준 출력 없음.
error[TPZ5008]: duplicate concurrent arm `value`--> example.tpz:3:5|3 | value: 2| ^^^^^example.tpz: 1 type diagnostic
- 통과 · expected nonzero; observed 1
- 통과 · checking TPZ5008; cause duplicate-concurrent-arm; primary location asserted; additional diagnostics allow
- 통과 · stdout matches the authored expectation
Timeout with no fallback block
링크let joined = concurrent(timeout: 25ms) {count: 2 + 3label: "ready"}
표준 출력 없음.error[TPZ2005]: the `concurrent(timeout: ...)` form requires an `else` block
--> example.tpz:1:14
|
1 | let joined = concurrent(timeout: 25ms) {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
example.tpz: 1 diagnostic
기대 결과
관측한 단언 · 통과 · 5.21.3
종료 코드 1.
표준 출력 없음.
error[TPZ2005]: the `concurrent(timeout: ...)` form requires an `else` block--> example.tpz:1:14|1 | let joined = concurrent(timeout: 25ms) {| ^^^^^^^^^^^^^^^^^^^^^^^^^^^example.tpz: 1 diagnostic
- 통과 · expected nonzero; observed 1
- 통과 · checking TPZ2005; cause concurrent-timeout-without-else; primary location asserted; additional diagnostics allow
- 통과 · stdout matches the authored expectation
An untimed group may have no arms
링크let joined = concurrent {}
표준 출력 없음.기대 결과
기대 결과
검사가 프로그램을 받아들입니다. 종료 코드 0.
Arm blocks keep their own names and read the enclosing binding
링크import std.iolet base: int = 10let joined = concurrent {first: {let branchLocal = base + 1branchLocal}second: {let branchLocal = base + 2branchLocal}}io.writeLine("{joined.first},{joined.second}")
11,12
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
11,12
An arm label does not bind a value
링크let joined = concurrent {first: 1second: first}
표준 출력 없음.기대 결과
An arm block does not expose its local to a sibling
링크let joined = concurrent {first: {let branchLocal = 1branchLocal}second: branchLocal}
표준 출력 없음.기대 결과
A noncompleting arm selects the timeout fallback
링크import std.iofunction waitForever() -> int {while (true) {}0}let joined = concurrent(timeout: 0ms) {value: waitForever()} else {{ value: 7 }}io.writeLine("{joined.value}")
7
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
7
The arm cannot complete, so this example does not choose a rule for completion coinciding with a deadline. It expects the complete fallback record.
Called functions and inner loops consume their own control transfer
링크import std.iofunction advance(value: Result<int, string>) -> Result<int, string> {let number: int = value?return Ok(number + 1)}let joined = concurrent {result: advance(Err("missing"))count: {let mut count = 0while (count < 3) {count += 1if (count < 3) { continue }break}count}}match (joined.result) {case Ok(_) => io.writeLine("unexpected")case Err(message) => io.writeLine(message)}io.writeLine("{joined.count}")
missing
3
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
missing3
Zero duration takes the fallback for one ready arm
링크import std.iolet joined = concurrent(timeout: 0ms) {value: 1} else {{ value: 7 }}io.writeLine("{joined.value}")
7
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
7
Zero duration takes the complete fallback for multiple ready arms
링크import std.iolet joined = concurrent(timeout: 0ms) {left: 1right: 2} else {{ left: 7, right: 8 }}io.writeLine("{joined.left}:{joined.right}")
7:8
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
7:8
A zero-duration fallback can raise its own runtime fault
링크function divide(divisor: int) -> int {42 / divisor}let joined = concurrent(timeout: 0ms) {value: 1} else {{ value: divide(0) }}
표준 출력 없음.기대 결과
An arm cannot return from its enclosing function
링크function escape() -> int {let joined = concurrent {value: {return 1}}0}
표준 출력 없음.기대 결과
An arm cannot break its enclosing loop
링크while (true) {let joined = concurrent {value: {break}}break}
표준 출력 없음.기대 결과
An arm cannot continue its enclosing loop
링크while (true) {let joined = concurrent {value: {continue}}break}
표준 출력 없음.기대 결과
An arm cannot propagate a Result to its enclosing function
링크function escape(value: Result<int, string>) -> Result<int, string> {let joined = concurrent {result: value?}Ok(0)}
표준 출력 없음.기대 결과
An arm cannot break to an outer loop label
링크loop 'outer {let joined = concurrent {value: {loop {break 'outer}}}break}
표준 출력 없음.기대 결과
An arm cannot continue to an outer loop label
링크loop 'outer {let joined = concurrent {value: {loop {continue 'outer}}}break}
표준 출력 없음.기대 결과
A function declared inside an arm consumes its own control transfers
링크import std.iolet joined = concurrent {result: {function advance(value: Result<int, string>) -> Result<int, string> {let number: int = value?return Ok(number + 1)}advance(Ok(4))}}match (joined.result) {case Ok(value) => io.writeLine("{value}")case Err(message) => io.writeLine(message)}
5
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
5
Parentheses preserve a timeout value
링크import std.iolet joined = concurrent(timeout: (0ms)) { value: 1 } else { { value: 7 } }io.writeLine("{joined.value}")
7
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
7
A bound Duration is the same timeout value
링크import std.iolet budget: Duration = 0mslet joined = concurrent(timeout: budget) { value: 1 } else { { value: 7 } }io.writeLine("{joined.value}")
7
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
7
Evaluate the timeout once before any arm
링크import std.iofunction budget() -> Duration {io.writeLine("budget")0ms}let joined = concurrent(timeout: budget()) {value: { io.writeLine("arm")1 }} else {io.writeLine("fallback"){ value: 7 }}io.writeLine("{joined.value}")
budget
fallback
7
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
budgetfallback7
The adopted zero-deadline rule selects the fallback before any arm. The budget marker occurs once and before fallback evaluation; no arm marker is permitted.
Functions and conditionals preserve Duration
링크function choose(first: bool) -> Duration { if (first) { 0ms } else { 1s } }let budget: Duration = choose(true)let joined = concurrent(timeout: if (true) { budget } else { 2m }) { value: 1 } else { { value: 7 } }
표준 출력 없음.기대 결과
기대 결과
검사가 프로그램을 받아들입니다. 종료 코드 0.
An integer does not implicitly become Duration
링크let joined = concurrent(timeout: 1) { value: 1 } else { { value: 7 } }
표준 출력 없음.기대 결과
The target uses the existing expected-type mismatch identity. Current SELF rejects earlier with TPZ2001; that already-nonzero exit is not excused.
A timeout expression fault precedes the group
링크import std.iofunction budget(divisor: int) -> Duration {io.writeLine("budget")let ratio = 42 / divisor0ms}let joined = concurrent(timeout: budget(0)) {value: { io.writeLine("arm")1 }} else {io.writeLine("fallback"){ value: 7 }}
budget
기대 결과
An empty concurrent record passes through an explicit record signature
링크import std.iofunction keep(value: {}) -> {} { value }let joined: {} = keep(concurrent {})io.writeLine("completed")
completed
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
completed
A zero deadline wins over empty-group completion
링크import std.iofunction deadline() -> Duration { io.writeLine("timeout")0ms }let joined: {} = concurrent(timeout: deadline()) {} else {io.writeLine("fallback")concurrent {}}io.writeLine("completed")
timeout
fallback
completed
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
timeoutfallbackcompleted
The zero-field record is distinct from unit
링크let joined: () = concurrent {}
표준 출력 없음.기대 결과
Use the empty structural type in aliases, parameters, and results
링크type Empty = {}function keep(value: Empty) -> {} { value }
표준 출력 없음.기대 결과
기대 결과
검사가 프로그램을 받아들입니다. 종료 코드 0.
A positive deadline ends an infinite loop
링크import std.iolet joined = concurrent(timeout: 1ms) {value: {while (true) {}0}} else {io.writeLine("fallback"){ value: 7 }}io.writeLine("{joined.value}")
fallback
7
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
fallback7
An abandoned arm skips its registered defer
링크import std.iolet joined = concurrent(timeout: 1ms) {value: {defer { io.writeLine("abandoned cleanup") }while (true) {}0}} else {io.writeLine("fallback"){ value: 7 }}
fallback
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
fallback
관측한 단언 · 통과 · 5.21.3
종료 코드 0.
fallback
표준 오류 출력 없음.
- 통과 · expected 0; observed 0
- 통과 · stdout matches the authored expectation
- 통과 · stderr matches the authored expectation
A normally completed arm runs its defer before the result
링크import std.iolet joined = concurrent(timeout: 10s) {value: {defer { io.writeLine("completed cleanup") }7}} else {io.writeLine("fallback"){ value: 0 }}io.writeLine("{joined.value}")
completed cleanup
7
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
completed cleanup7
관측한 단언 · 통과 · 5.21.3
종료 코드 0.
completed cleanup7
표준 오류 출력 없음.
- 통과 · expected 0; observed 0
- 통과 · stdout matches the authored expectation
- 통과 · stderr matches the authored expectation
This example exercises an arm that completes before the deadline. Its defer output precedes the group result.
Completed cleanup followed by an abandoned arm
링크import std.iolet completed = concurrent {value: {defer { io.writeLine("completed cleanup") }1}}let abandoned = concurrent(timeout: 1s) {value: {defer { io.writeLine("abandoned cleanup") }while (true) {}0}} else {io.writeLine("fallback"){ value: 0 }}
completed cleanup
fallback
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
completed cleanupfallback
관측한 단언 · 통과 · 5.21.3
종료 코드 0.
completed cleanupfallback
표준 오류 출력 없음.
- 통과 · expected 0; observed 0
- 통과 · stdout matches the authored expectation
- 통과 · stderr matches the authored expectation
The first group has no deadline and completes before the timed group starts. The second group abandons its nonterminating arm. Separate groups test completed cleanup and skipped abandoned cleanup without assuming any arm start or completion order.
Pending contracts
링크Related language and diagnostic references
링크Functions and generics defines the calls used by arms. Failure and propagation defines the language propagation form. Types, Bindings and scope, and Blocks and control flow own their respective general rules.
The diagnostic catalog owns complete diagnostic descriptions. The following entries are referenced here for the specific causes isolated by these examples.