Skip to content

Spawned work, channels, and select

Use spawn when the work itself is a value you want to own and join. A block spawn returns Spawned: Join()/Wait() block and rethrow its failure, WaitAsync() is the non-blocking counterpart, and the handle itself is awaitable. A task func returns Spawned<T> when it declares -> T; its Wait, Join, and await produce that T.

func run() -> int {
let work = spawn { log("started") }
work.Join()
return 42
}

task func gives the same idea a function declaration. Calling it launches concurrent work; it does not run the body inline. Parameters are ordinary immutable inputs to that invocation.

task func scale(value: int, factor: int) -> int = value * factor
func run() -> int {
let future = scale(6, 7) // starts now; no warning or error for not awaiting here
return future.Wait() // join later at the boundary that needs the result
}

chan<T>(capacity) is a typed, bounded channel. A producer should close it when no more values will arrive; for value in ch then terminates naturally after the buffered values drain.

func sum() -> int {
let ch = chan<int>(2)
let producer = spawn {
defer { ch.Close() }
ch.Send(20)
ch.Send(22)
}
var total = 0
for value in ch { total += value }
producer.Wait()
return total
}

Do not share a captured mutable var with a task func; use a channel, a pointer-backed explicitly shared object, an explicit parameter, or a let capture instead. That keeps the ownership edge visible in source.

select chooses one ready send or receive arm. Without default, it blocks. A timeout arm bounds that wait; with default, the select becomes a poll.

func receiveOrTimeout(ch: Chan<int>) -> int {
var result = -1
select {
.recv(value, ch) { result = value }
.timeout(50) { result = 0 }
}
return result
}

Use select to choose between channel operations, not as a general replacement for if. The compiler lowers the arms into the stdlib’s channel-selection primitive, preserving the chosen operation’s value and side effect.