Chapter 9

Concurrency

New in 0.3.0: spawn starts a thread, and channels carry values between threads. Sure's concurrency is fearless*, for the same reason everything else about it is.

spawn

spawn f(args) runs the call on its own operating-system thread and moves on immediately. Any of your functions can be spawned; its return value, if any, goes nowhere. There is no join and no thread handle — if you want to know a thread finished, it can tell you, on a channel.

Channels

A channel of T has type chan T. Like an empty collection, a new channel learns its type from its declaration; the optional argument is a buffer capacity.

func shout(inbox: chan String) {
    print("received: " + recv(inbox))
}

func main() -> Int {
    let inbox: chan String = channel()      // unbuffered
    spawn shout(inbox)
    send(inbox, "hello from the main thread")
    return 0
}

for, over a channel

The idiomatic receive loop is for, which takes values until the channel closes and then simply ends — no abort, no sentinel:

func squares(nums: chan Int, n: Int) {
    for i in 1..n + 1 {
        send(nums, i * i)
    }
    close(nums)
}

func report(nums: chan Int, done: chan String) {
    var total = 0
    for v in nums {
        total = total + v
    }
    send(done, "total: " + str(total))
}

func main() -> Int {
    let nums: chan Int = channel(4)
    let done: chan String = channel()
    spawn squares(nums, 5)
    spawn report(nums, done)
    print(recv(done))    // total: 55
    return 0
}

This is the pipeline pattern, and it is most of what you need: producers send, consumers for, the final answer arrives on a channel that main is holding. Note how main waits — not with a join, but by receiving. In Sure you wait for work the same way you get its results, which keeps the design honest about which is which.

Share memory by communicating

Nothing stops you from handing the same array to two spawned functions. The language will compile it; the threads will interleave; the result will be what it will be. Sure has no locks to offer you and no intention of acquiring any — the supported design is the one you just read: give each thread its own values and pass what must travel through channels. A value that has been sent should be treated as gone from the sender's world; it changed hands at the moment of the send, and chapter 7's machinery schedules both sides accordingly.

Fearless concurrency*. The asterisk is the same asterisk as always. Concurrent deallocation is described in MEMORY.md with a sentence worth reading twice: "the inference simply considers all threads." Data races are absent in every case where they are absent. Write the pipeline pattern and both you and the schedule will be fine; write clever shared-state code and you will learn things about your program that sure blame will later explain to you with great confidence.