Skip to content
TE976 · Type · error

Non-expression statement inside `parallel { }`

`parallel { … }` runs each top-level statement as its own concurrent task, so every statement in the block must be an expression that can become one.

Why this fires

parallel { … } runs each of its top-level statements as its own concurrent task — the block desugars to the structured-concurrency form you could write by hand:

parallel {                 scope {
    sh"cargo build".run()      spawn { sh"cargo build".run() }
    sh"cargo test".run()   ⇒   spawn { sh"cargo test".run() }
}                          }

Because every statement becomes an isolated spawn, a statement whose meaning depends on its siblings can't work there:

parallel {
    let out = sh"git tag".run()   // error[TE976] — no sibling could ever
    print(out)                    // read `out`; each line is its own task
}

let, return, break/continue, and defer are rejected: a binding would be invisible to the other tasks, and control flow would cross task boundaries.

Fix it

1. Keep only independent expression statements

parallel {
    sh"cargo build --target x86_64".run()
    sh"cargo build --target aarch64".run()
    sh"cargo build --target wasm32".run()
}

2. Need results or coordination? Use scope + channel() directly

parallel is only sugar — the full form gives you bindings and communication:

let ch = channel()
scope {
    spawn { ch.send(sh"git rev-parse HEAD".run()) }
    spawn { ch.send(sh"git status --short".run()) }
}
let a = ch.recv()
let b = ch.recv()

Notes

  • The desugared scope semantics apply: the block only exits when every task finished, and a panic in one task cancels the siblings.
  • parallel is a contextual keyword — parallel(fs) still calls a function named parallel (e.g. std::effect's), and only parallel { … } forms the block.

See also

  • examples/features/16-concurrency/12-scope-spawn.zolo — the underlying A8 form.
  • specs/shell-scripting.html §11 — design and admission rule.

Search Zolo

9 results

en