Skip to content
Topaz6.0 · Before release
Download

Search documentation

Specification 6.0 · Not released · Tested product 5.21.3

Use arrow keys to choose a result. Press Enter to open.

Type an API name, diagnostic number, rule, or project topic.

    Language · chapter 14 · 17 rules

    Concurrency

    Write and check concurrent expressions with this reference. The examples cover result fields, duration spellings, and rejected forms.

    Scope and ownership

    Link

    This page targets Topaz 5.21 language mode and the public 5.21.3 CLI. The rules cite SELF source and its product runtime at af88afbc1d99a8c0220b5984f9d74f895f8156f2.

    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.

    Concurrent forms

    Link
    EBNF
    1. Concurrent = Join | Timed ;
    2. Join = "concurrent" "{" ArmSequence? "}" ;
    3. Timed = "concurrent" "(" "timeout" ":" Expression ")"
    4. "{" ArmSequence? "}" "else" Block ;
    5. Arm = Identifier ":" Expression ;
    6. DurationLiteral = DecimalInteger DurationUnit ;
    7. DurationUnit = "ms" | "s" | "m" ;

    This grammar imports separators, expression grouping, blocks, and decimal integer spelling from their language chapters. ArmSequence names the arm sequence.

    Source evidence
    Ruletopaz.rule.concurrency.arm-form

    Named arm expressions

    Each concurrent arm has the form name: expression, where name is an identifier.

    Why

    Arm labels become record fields. The same name: value shape connects each computation to the field that will hold its result.

    Source evidence
    Ruletopaz.rule.concurrency.nonempty-arms

    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.

    Why

    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.

    Source evidence
    Ruletopaz.rule.concurrency.timeout-pairing

    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.

    Why

    A timed group can finish without a complete result record. A fallback supplies the value or control transfer for that path.

    Source evidence
    Ruletopaz.rule.concurrency.timeout-literal

    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.

    Why

    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.

    Source evidence

    Arms and result fields

    Link
    Ruletopaz.rule.concurrency.arm-name-uniqueness

    Distinct arm names

    Arm names in one concurrent expression must be distinct. A repeated arm name is rejected with TPZ5008.

    Why

    Each arm supplies one result field. Repeated names would make that field ambiguous or silently discard an arm value.

    Source evidence
    Ruletopaz.rule.concurrency.result-record

    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.

    Why

    Named fields preserve which computation produced each value regardless of completion order. Ordinary record access can then read the results.

    Source evidence
    Ruletopaz.rule.concurrency.arm-scope-and-order

    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.

    Why

    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.

    Source evidence
    Ruletopaz.rule.concurrency.arm-control-boundary

    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.

    Why

    Each arm runs independently. Keeping control transfers inside its boundary avoids making sibling cancellation depend on a return or loop outside the arm.

    Source evidence

    Completion and failure

    Link
    Ruletopaz.rule.concurrency.successful-completion

    Collect completed values

    When every arm finishes successfully before any applicable timeout, the concurrent expression produces a record containing the named arm values.

    Why

    A completed record must contain every declared field. Waiting for all arms prevents callers from receiving missing results.

    Source evidence
    Ruletopaz.rule.concurrency.ordinary-result-values

    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.

    Why

    Result variants are ordinary values until a program handles or propagates them. Preserving each variant keeps concurrency from adding implicit propagation.

    Source evidence
    Ruletopaz.rule.concurrency.execution-failure

    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.

    Why

    The fallback handles an expired deadline. Using it for an arm fault would hide that failure and give the fallback two different meanings.

    Source evidence
    Ruletopaz.rule.concurrency.timeout-fallback

    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.

    Why

    The fallback replaces the group result as a whole. Discarding collected values prevents the returned fields from depending on scheduling.

    Source evidence
    Ruletopaz.rule.concurrency.deadline-race

    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.

    Why

    Giving the deadline priority makes a boundary case reproducible, including a zero timeout. Fault priority does not change when independent arms are reordered.

    Source evidence
    Ruletopaz.rule.concurrency.timed-progress

    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 the remaining abandoned-arm cleanup question.

    Why

    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.

    Source evidence

    Duration and fallback values

    Link
    Ruletopaz.rule.concurrency.duration-conversion

    Duration units

    Why

    A shared literal definition keeps timeout expressions consistent with ordinary expressions.

    Source evidence
    Ruletopaz.rule.concurrency.duration-range

    Duration literal bounds

    Why

    Keeping literal bounds in one place prevents timeout syntax from acquiring a separate value range.

    Source evidence
    Ruletopaz.rule.concurrency.fallback-type

    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.

    Why

    Successful completion and the fallback feed the same expression. Matching their record types lets callers use the result without testing which path completed.

    Source evidence

    Examples and expected checks

    Link

    Read both completed arm values

    Link
    example.tpzRead both completed arm valuesOpen in Playground
    1. import std.io
    2. let joined = concurrent {
    3. first: 2
    4. second: 3
    5. }
    6. io.writeLine("{joined.first}")
    7. io.writeLine("{joined.second}")
    Recorded outputexit 0
    2
    3
    
    $ topaz run example.tpz --compiler self · recorded with topaz 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. 2
    2. 3
    Observed assertions · passed · 5.21.3

    Exit code 0.

    Observed stdout
    1. 2
    2. 3

    No stderr.

    • Passed · expected 0; observed 0
    • Passed · stdout matches the authored expectation
    • Passed · stderr matches the authored expectation
    Source evidence

    Keep an Err value in the result record

    Link
    example.tpzKeep an Err value in the result recordOpen in Playground
    1. import std.io
    2. function completeError() -> Result<int, string> {
    3. Err("missing")
    4. }
    5. let joined = concurrent {
    6. value: completeError()
    7. count: 7
    8. }
    9. match (joined.value) {
    10. case Ok(_) => io.writeLine("unexpected")
    11. case Err(message) => io.writeLine(message)
    12. }
    13. io.writeLine("{joined.count}")
    Recorded outputexit 0
    missing
    7
    
    $ topaz run example.tpz --compiler self · recorded with topaz 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. missing
    2. 7
    Observed assertions · passed · 5.21.3

    Exit code 0.

    Observed stdout
    1. missing
    2. 7

    No stderr.

    • Passed · expected 0; observed 0
    • Passed · stdout matches the authored expectation
    • Passed · stderr matches the authored expectation
    Source evidence

    Distinct arm names and result fields

    Link
    example.tpzDistinct arm names and result fieldsOpen in Playground
    1. let joined = concurrent {
    2. first: 1
    3. second: 2
    4. }
    5. let first: int = joined.first
    6. let second: int = joined.second
    Recorded outputexit 0
    No stdout.
    $ topaz check example.tpz --compiler self --format json · recorded with topaz 5.21.3
    Expected result

    Expected result

    Checking accepts the program. Exit code 0.

    Observed assertions · passed · 5.21.3

    Exit code 0.

    No stdout.

    No stderr.

    • Passed · expected 0; observed 0
    • Passed · stdout matches the authored expectation
    • Passed · stderr matches the authored expectation
    Source evidence

    A fallback with the corresponding result fields

    Link
    example.tpzA fallback with the corresponding result fieldsOpen in Playground
    1. let joined = concurrent(timeout: 25ms) {
    2. count: 2 + 3
    3. label: "ready"
    4. } else {
    5. { count: 0, label: "timeout" }
    6. }
    7. let count: int = joined.count
    8. let label: string = joined.label
    Recorded outputexit 0
    No stdout.
    $ topaz check example.tpz --compiler self --format json · recorded with topaz 5.21.3
    Expected result

    Expected result

    Checking accepts the program. Exit code 0.

    Observed assertions · passed · 5.21.3

    Exit code 0.

    No stdout.

    No stderr.

    • Passed · expected 0; observed 0
    • Passed · stdout matches the authored expectation
    • Passed · stderr matches the authored expectation
    Source evidence

    The three duration units

    Link
    example.tpzThe three duration unitsOpen in Playground
    1. let milliseconds = concurrent(timeout: 1ms) { value: 1 } else { { value: 0 } }
    2. let seconds = concurrent(timeout: 1s) { value: 1 } else { { value: 0 } }
    3. let minutes = concurrent(timeout: 1m) { value: 1 } else { { value: 0 } }
    Recorded outputexit 0
    No stdout.
    $ topaz check example.tpz --compiler self --format json · recorded with topaz 5.21.3
    Expected result

    Expected result

    Checking accepts the program. Exit code 0.

    Observed assertions · passed · 5.21.3

    Exit code 0.

    No stdout.

    No stderr.

    • Passed · expected 0; observed 0
    • Passed · stdout matches the authored expectation
    • Passed · stderr matches the authored expectation
    Source evidence

    Repeated name in one concurrent expression

    Link
    example.tpzRepeated name in one concurrent expressionOpen in Playground
    1. let joined = concurrent {
    2. value: 1
    3. value: 2
    4. }
    Recorded outputexit 1
    No stdout.
    error[TPZ5008]: duplicate concurrent arm `value`
     --> example.tpz:3:5
      |
    3 |     value: 2
      |     ^^^^^
    
    example.tpz: 1 type diagnostic
    
    $ topaz check example.tpz --compiler self · recorded with topaz 5.21.3
    Expected result

    Expected result

    Rejection during check. Nonzero exit.

    • TPZ5008 duplicate-concurrent-arm at example.tpz:3:5
    Observed assertions · passed · 5.21.3

    Exit code 1.

    No stdout.

    Observed stderr
    1. error[TPZ5008]: duplicate concurrent arm `value`
    2. --> example.tpz:3:5
    3. |
    4. 3 | value: 2
    5. | ^^^^^
    6. example.tpz: 1 type diagnostic
    • Passed · expected nonzero; observed 1
    • Passed · checking TPZ5008; cause duplicate-concurrent-arm; primary location asserted; additional diagnostics allow
    • Passed · stdout matches the authored expectation
    Source evidence

    Timeout with no fallback block

    Link
    example.tpzTimeout with no fallback blockOpen in Playground
    1. let joined = concurrent(timeout: 25ms) {
    2. count: 2 + 3
    3. label: "ready"
    4. }
    Recorded outputexit 1
    No stdout.
    error[TPZ2005]: the `concurrent(timeout: ...)` form requires an `else` block
     --> example.tpz:1:14
      |
    1 | let joined = concurrent(timeout: 25ms) {
      |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^
    
    example.tpz: 1 diagnostic
    
    $ topaz check example.tpz --compiler self · recorded with topaz 5.21.3
    Expected result

    Expected result

    Rejection during parse. Nonzero exit.

    • TPZ2005 concurrent-timeout-without-else at example.tpz:1:14
    Observed assertions · passed · 5.21.3

    Exit code 1.

    No stdout.

    Observed stderr
    1. error[TPZ2005]: the `concurrent(timeout: ...)` form requires an `else` block
    2. --> example.tpz:1:14
    3. |
    4. 1 | let joined = concurrent(timeout: 25ms) {
    5. | ^^^^^^^^^^^^^^^^^^^^^^^^^^^
    6. example.tpz: 1 diagnostic
    • Passed · expected nonzero; observed 1
    • Passed · checking TPZ2005; cause concurrent-timeout-without-else; primary location asserted; additional diagnostics allow
    • Passed · stdout matches the authored expectation
    Source evidence

    An untimed group may have no arms

    Link
    example.tpzAn untimed group may have no armsOpen in Playground
    1. let joined = concurrent {}
    Expected outputexit 0
    No stdout.
    $ topaz check example.tpz --compiler self --format json · target product 5.21.3
    Expected result

    Expected result

    Checking accepts the program. Exit code 0.

    Source evidence

    Arm blocks keep their own names and read the enclosing binding

    Link
    example.tpzArm blocks keep their own names and read the enclosing bindingOpen in Playground
    1. import std.io
    2. let base: int = 10
    3. let joined = concurrent {
    4. first: {
    5. let branchLocal = base + 1
    6. branchLocal
    7. }
    8. second: {
    9. let branchLocal = base + 2
    10. branchLocal
    11. }
    12. }
    13. io.writeLine("{joined.first},{joined.second}")
    Expected outputexit 0
    11,12
    
    $ topaz run example.tpz --compiler self · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. 11,12
    Source evidence

    An arm label does not bind a value

    Link
    example.tpzAn arm label does not bind a valueOpen in Playground
    1. let joined = concurrent {
    2. first: 1
    3. second: first
    4. }
    Expected outputNonzero exit
    No stdout.
    $ topaz check example.tpz --compiler self --format json · target product 5.21.3
    Expected result

    Expected result

    Rejection during check. Nonzero exit.

    • TPZ5002 concurrent-arm-label-not-bound at example.tpz:3:13
    Source evidence

    An arm block does not expose its local to a sibling

    Link
    example.tpzAn arm block does not expose its local to a siblingOpen in Playground
    1. let joined = concurrent {
    2. first: {
    3. let branchLocal = 1
    4. branchLocal
    5. }
    6. second: branchLocal
    7. }
    Expected outputNonzero exit
    No stdout.
    $ topaz check example.tpz --compiler self --format json · target product 5.21.3
    Expected result

    Expected result

    Rejection during check. Nonzero exit.

    • TPZ5002 concurrent-sibling-local-not-bound at example.tpz:6:13
    Source evidence

    A noncompleting arm selects the timeout fallback

    Link
    example.tpzA noncompleting arm selects the timeout fallbackOpen in Playground
    1. import std.io
    2. function waitForever() -> int {
    3. while (true) {}
    4. 0
    5. }
    6. let joined = concurrent(timeout: 0ms) {
    7. value: waitForever()
    8. } else {
    9. { value: 7 }
    10. }
    11. io.writeLine("{joined.value}")
    Expected outputexit 0
    7
    
    $ topaz run example.tpz --compiler self · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. 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.

    Source evidence

    Called functions and inner loops consume their own control transfer

    Link
    example.tpzCalled functions and inner loops consume their own control transferOpen in Playground
    1. import std.io
    2. function advance(value: Result<int, string>) -> Result<int, string> {
    3. let number: int = value?
    4. return Ok(number + 1)
    5. }
    6. let joined = concurrent {
    7. result: advance(Err("missing"))
    8. count: {
    9. let mut count = 0
    10. while (count < 3) {
    11. count += 1
    12. if (count < 3) { continue }
    13. break
    14. }
    15. count
    16. }
    17. }
    18. match (joined.result) {
    19. case Ok(_) => io.writeLine("unexpected")
    20. case Err(message) => io.writeLine(message)
    21. }
    22. io.writeLine("{joined.count}")
    Expected outputexit 0
    missing
    3
    
    $ topaz run example.tpz --compiler self · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. missing
    2. 3
    Source evidence

    Zero duration takes the fallback for one ready arm

    Link
    example.tpzZero duration takes the fallback for one ready armOpen in Playground
    1. import std.io
    2. let joined = concurrent(timeout: 0ms) {
    3. value: 1
    4. } else {
    5. { value: 7 }
    6. }
    7. io.writeLine("{joined.value}")
    Expected outputexit 0
    7
    
    $ topaz run example.tpz --compiler self · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. 7
    Source evidence

    Zero duration takes the complete fallback for multiple ready arms

    Link
    example.tpzZero duration takes the complete fallback for multiple ready armsOpen in Playground
    1. import std.io
    2. let joined = concurrent(timeout: 0ms) {
    3. left: 1
    4. right: 2
    5. } else {
    6. { left: 7, right: 8 }
    7. }
    8. io.writeLine("{joined.left}:{joined.right}")
    Expected outputexit 0
    7:8
    
    $ topaz run example.tpz --compiler self · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. 7:8
    Source evidence

    A zero-duration fallback can raise its own runtime fault

    Link
    example.tpzA zero-duration fallback can raise its own runtime faultOpen in Playground
    1. function divide(divisor: int) -> int {
    2. 42 / divisor
    3. }
    4. let joined = concurrent(timeout: 0ms) {
    5. value: 1
    6. } else {
    7. { value: divide(0) }
    8. }
    Expected outputexit 1
    No stdout.
    $ topaz run example.tpz --compiler self · target product 5.21.3
    Expected result

    Expected result

    Rejection during run. Exit code 1.

    • TPZ4002 diagnostic-tpz4002 at example.tpz:2:5
    Source evidence

    An arm cannot return from its enclosing function

    Link
    example.tpzAn arm cannot return from its enclosing functionOpen in Playground
    1. function escape() -> int {
    2. let joined = concurrent {
    3. value: {
    4. return 1
    5. }
    6. }
    7. 0
    8. }
    Expected outputNonzero exit
    No stdout.
    $ topaz check example.tpz --compiler self --format json · target product 5.21.3
    Expected result

    Expected result

    Rejection during check. Nonzero exit.

    • TPZ5001 concurrent-arm-function-boundary at example.tpz:4:13
    Source evidence

    An arm cannot break its enclosing loop

    Link
    example.tpzAn arm cannot break its enclosing loopOpen in Playground
    1. while (true) {
    2. let joined = concurrent {
    3. value: {
    4. break
    5. }
    6. }
    7. break
    8. }
    Expected outputNonzero exit
    No stdout.
    $ topaz check example.tpz --compiler self --format json · target product 5.21.3
    Expected result

    Expected result

    Rejection during check. Nonzero exit.

    • TPZ5001 concurrent-arm-loop-boundary at example.tpz:4:13
    Source evidence

    An arm cannot continue its enclosing loop

    Link
    example.tpzAn arm cannot continue its enclosing loopOpen in Playground
    1. while (true) {
    2. let joined = concurrent {
    3. value: {
    4. continue
    5. }
    6. }
    7. break
    8. }
    Expected outputNonzero exit
    No stdout.
    $ topaz check example.tpz --compiler self --format json · target product 5.21.3
    Expected result

    Expected result

    Rejection during check. Nonzero exit.

    • TPZ5001 concurrent-arm-loop-boundary at example.tpz:4:13
    Source evidence

    An arm cannot propagate a Result to its enclosing function

    Link
    example.tpzAn arm cannot propagate a Result to its enclosing functionOpen in Playground
    1. function escape(value: Result<int, string>) -> Result<int, string> {
    2. let joined = concurrent {
    3. result: value?
    4. }
    5. Ok(0)
    6. }
    Expected outputNonzero exit
    No stdout.
    $ topaz check example.tpz --compiler self --format json · target product 5.21.3
    Expected result

    Expected result

    Rejection during check. Nonzero exit.

    • TPZ5001 concurrent-arm-function-boundary at example.tpz:3:17
    Source evidence

    An arm cannot break to an outer loop label

    Link
    example.tpzAn arm cannot break to an outer loop labelOpen in Playground
    1. loop 'outer {
    2. let joined = concurrent {
    3. value: {
    4. loop {
    5. break 'outer
    6. }
    7. }
    8. }
    9. break
    10. }
    Expected outputNonzero exit
    No stdout.
    $ topaz check example.tpz --compiler self --format json · target product 5.21.3
    Expected result

    Expected result

    Rejection during check. Nonzero exit.

    • TPZ5720 concurrent-arm-loop-label at example.tpz:5:17
    Source evidence

    An arm cannot continue to an outer loop label

    Link
    example.tpzAn arm cannot continue to an outer loop labelOpen in Playground
    1. loop 'outer {
    2. let joined = concurrent {
    3. value: {
    4. loop {
    5. continue 'outer
    6. }
    7. }
    8. }
    9. break
    10. }
    Expected outputNonzero exit
    No stdout.
    $ topaz check example.tpz --compiler self --format json · target product 5.21.3
    Expected result

    Expected result

    Rejection during check. Nonzero exit.

    • TPZ5720 concurrent-arm-loop-label at example.tpz:5:17
    Source evidence

    A function declared inside an arm consumes its own control transfers

    Link
    example.tpzA function declared inside an arm consumes its own control transfersOpen in Playground
    1. import std.io
    2. let joined = concurrent {
    3. result: {
    4. function advance(value: Result<int, string>) -> Result<int, string> {
    5. let number: int = value?
    6. return Ok(number + 1)
    7. }
    8. advance(Ok(4))
    9. }
    10. }
    11. match (joined.result) {
    12. case Ok(value) => io.writeLine("{value}")
    13. case Err(message) => io.writeLine(message)
    14. }
    Expected outputexit 0
    5
    
    $ topaz run example.tpz --compiler self · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. 5
    Source evidence

    Parentheses preserve a timeout value

    Link
    example.tpzParentheses preserve a timeout valueOpen in Playground
    1. import std.io
    2. let joined = concurrent(timeout: (0ms)) { value: 1 } else { { value: 7 } }
    3. io.writeLine("{joined.value}")
    Expected outputexit 0
    7
    
    $ topaz run example.tpz --compiler self · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. 7
    Source evidence

    A bound Duration is the same timeout value

    Link
    example.tpzA bound Duration is the same timeout valueOpen in Playground
    1. import std.io
    2. let budget: Duration = 0ms
    3. let joined = concurrent(timeout: budget) { value: 1 } else { { value: 7 } }
    4. io.writeLine("{joined.value}")
    Expected outputexit 0
    7
    
    $ topaz run example.tpz --compiler self · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. 7
    Source evidence

    Evaluate the timeout once before any arm

    Link
    example.tpzEvaluate the timeout once before any armOpen in Playground
    1. import std.io
    2. function budget() -> Duration {
    3. io.writeLine("budget")
    4. 0ms
    5. }
    6. let joined = concurrent(timeout: budget()) {
    7. value: { io.writeLine("arm")
    8. 1 }
    9. } else {
    10. io.writeLine("fallback")
    11. { value: 7 }
    12. }
    13. io.writeLine("{joined.value}")
    Expected outputexit 0
    budget
    fallback
    7
    
    $ topaz run example.tpz --compiler self · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. budget
    2. fallback
    3. 7

    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.

    Source evidence

    Functions and conditionals preserve Duration

    Link
    example.tpzFunctions and conditionals preserve DurationOpen in Playground
    1. function choose(first: bool) -> Duration { if (first) { 0ms } else { 1s } }
    2. let budget: Duration = choose(true)
    3. let joined = concurrent(timeout: if (true) { budget } else { 2m }) { value: 1 } else { { value: 7 } }
    Expected outputexit 0
    No stdout.
    $ topaz check example.tpz --compiler self --format json · target product 5.21.3
    Expected result

    Expected result

    Checking accepts the program. Exit code 0.

    Source evidence

    An integer does not implicitly become Duration

    Link
    example.tpzAn integer does not implicitly become DurationOpen in Playground
    1. let joined = concurrent(timeout: 1) { value: 1 } else { { value: 7 } }
    Expected outputexit 1
    No stdout.
    $ topaz check example.tpz --compiler self · target product 5.21.3
    Expected result

    Expected result

    Rejection during check. Exit code 1.

    • TPZ5001 concurrent-timeout-duration-type at example.tpz:1:34

    The target uses the existing expected-type mismatch identity. Current SELF rejects earlier with TPZ2001; that already-nonzero exit is not excused.

    Source evidence

    A timeout expression fault precedes the group

    Link
    example.tpzA timeout expression fault precedes the groupOpen in Playground
    1. import std.io
    2. function budget(divisor: int) -> Duration {
    3. io.writeLine("budget")
    4. let ratio = 42 / divisor
    5. 0ms
    6. }
    7. let joined = concurrent(timeout: budget(0)) {
    8. value: { io.writeLine("arm")
    9. 1 }
    10. } else {
    11. io.writeLine("fallback")
    12. { value: 7 }
    13. }
    Expected outputexit 1
    budget
    
    $ topaz run example.tpz --compiler self · target product 5.21.3
    Expected result

    Expected result

    Rejection during run. Exit code 1.

    • TPZ4002 diagnostic-tpz4002 at example.tpz:4:17
    Expected stdout
    1. budget
    Source evidence

    An empty concurrent record passes through an explicit record signature

    Link
    example.tpzAn empty concurrent record passes through an explicit record signatureOpen in Playground
    1. import std.io
    2. function keep(value: {}) -> {} { value }
    3. let joined: {} = keep(concurrent {})
    4. io.writeLine("completed")
    Expected outputexit 0
    completed
    
    $ topaz run example.tpz --compiler self · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. completed
    Source evidence

    A zero deadline wins over empty-group completion

    Link
    example.tpzA zero deadline wins over empty-group completionOpen in Playground
    1. import std.io
    2. function deadline() -> Duration { io.writeLine("timeout")
    3. 0ms }
    4. let joined: {} = concurrent(timeout: deadline()) {} else {
    5. io.writeLine("fallback")
    6. concurrent {}
    7. }
    8. io.writeLine("completed")
    Expected outputexit 0
    timeout
    fallback
    completed
    
    $ topaz run example.tpz --compiler self · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. timeout
    2. fallback
    3. completed
    Source evidence

    The zero-field record is distinct from unit

    Link
    example.tpzThe zero-field record is distinct from unitOpen in Playground
    1. let joined: () = concurrent {}
    Expected outputNonzero exit
    No stdout.
    $ topaz check example.tpz --compiler self --format json · target product 5.21.3
    Expected result

    Expected result

    Rejection during check. Nonzero exit.

    • TPZ5001 concurrent-empty-result-is-not-unit at example.tpz:1:18
    Source evidence

    Use the empty structural type in aliases, parameters, and results

    Link
    example.tpzUse the empty structural type in aliases, parameters, and resultsOpen in Playground
    1. type Empty = {}
    2. function keep(value: Empty) -> {} { value }
    Expected outputexit 0
    No stdout.
    $ topaz check example.tpz --compiler self --format json · target product 5.21.3
    Expected result

    Expected result

    Checking accepts the program. Exit code 0.

    Source evidence

    A positive deadline ends an infinite loop

    Link
    main.tpzA positive deadline ends an infinite loopOpen in Playground
    1. import std.io
    2. let joined = concurrent(timeout: 1ms) {
    3. value: {
    4. while (true) {}
    5. 0
    6. }
    7. } else {
    8. io.writeLine("fallback")
    9. { value: 7 }
    10. }
    11. io.writeLine("{joined.value}")
    Expected outputexit 0
    fallback
    7
    
    $ topaz run main.tpz --compiler self · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. fallback
    2. 7
    Source evidence

    Pending contracts

    Link

    Related language and diagnostic references

    Link

    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.

    Unbound name

    Unexpected token

    Malformed concurrent form

    Redeclaration

    TPZ5001

    Division or remainder by zero

    Unknown loop label

    Menu

    Download