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
scopesemantics apply: the block only exits when every task finished, and a panic in one task cancels the siblings. parallelis a contextual keyword —parallel(fs)still calls a function namedparallel(e.g.std::effect's), and onlyparallel { … }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.