The Decision Not to Build Something
A complete concurrency story is supposed to include atomics, mutexes, and read-write locks. Mere's roadmap had them queued — and then didn't build them, on purpose. The choice was made by demand, not a checklist: which programs actually need them? Running examples answered, one of them quantifying the exact cost of the gap — and the honest conclusion was revised a step when a genuine case turned up that the first analysis had dismissed.
Concurrency now works: safe primitives, a humane combinator, four backends in agreement. But the roadmap had more queued — the lower-level shared-state machinery a “complete” concurrency story is expected to have: atomics, mutexes, read-write locks. This closing episode of the part is about deciding not to build them, at least not yet, and about the fact that deciding well what not to build is a real engineering act — one done by measuring demand rather than following a checklist.
The checklist would say yes; demand was asked instead
A feature checklist for a concurrency system lists atomics and locks without hesitation; every serious language has them. The more useful question is narrower: which programs actually need them, given what already exists? Working examples answered, and the answer was mostly “not these.”
Most problems that look like shared mutable state are really map-reduce, and channels already cover them. Counting how many items satisfy a predicate looks like a shared counter, but it is just each worker returning zero or one and the results summed — no lock, no atomic, and it runs across the backends unchanged. Problems that genuinely hold state can be written as an actor: one worker owns the state, and the others send it requests and get replies over a channel — Erlang’s “share memory by communicating,” and Mere’s channels are many-to-many, so a work queue falls out of the same mechanism. And read-only sharing already works with no new feature at all: an immutable value is shareable, so several workers can capture and read one large table or config concurrently. Across those three shapes — reduce, actor, shared read — the existing primitives are enough.
Which leaves a precise residue: atomics and locks genuinely help only for performance. A hot counter is far cheaper as an atomic increment than as a message to an actor. Read-heavy shared state is faster behind a read-write lock, which lets reads run concurrently, than behind an actor that serializes every access through one thread. The initial judgment followed: functionally, channels and actors cover shared state completely, so this machinery is a performance optimization, and the right time to build it is when a real benchmark shows a real bottleneck — which will also define exactly what the API needs to be. Until then, defer. It is the same demand-driven discipline that deferred the package manager and left the more general type-system machinery on the shelf: don’t build for a need you can’t yet point to.
The honest revision
That tidy conclusion — “pure performance, defer” — got revised one step, because a harder look turned up a case it had dismissed. Some parallel algorithms read shared mutable state mid-run to decide what to do next, and branch-and-bound search is the clean example: as it explores, it keeps a running best result and uses it to prune branches that can’t beat it.
A small example measured the cost precisely. The same maximization run two ways — sequentially, with one shared running best, and in parallel across four workers, each with its own local best — found the same answer, but at very different cost: the sequential version did one expensive evaluation, the parallel version did four. The parallel workers, unable to cheaply read each other’s running best, each evaluated what a shared best would have let them skip. Message passing gives the right answer but can’t share a live value cheaply enough to keep the pruning, so the algorithm’s quality degrades even though its result is correct.
That is not pure performance; it is the algorithm getting worse, and it meant the earlier conclusion was too tidy. There is a genuine motivator for atomics after all — branch-and-bound, adaptive search, a live shared budget. The honest correction runs in both directions at once: the case is real, so “just a performance nicety” was an overclaim to walk back; but it is also not a hard capability gap, because the parallel program still produces the correct answer — so “we can’t express this” would be an overclaim too. The measured truth is the precise one: expressible and correct today, but at a quantified loss of pruning that a shared atomic would recover.
Deferral with a receipt
So the decision held — defer — but deferral done properly is not the same as dropping. The example that found the gap also specifies what would close it: a shared atomic scalar with read and compare-and-update operations, marked shareable across threads, and a read-write lock for structured shared state, with the usage pattern spelled out (workers read the shared best to prune, update it atomically when they beat it). The trigger is named too: build this when branch-and-bound or adaptive parallel search becomes a real target. This is a deferred question with a receipt — the cost of not having it is measured, the API it would need is drafted, and the condition that should un-defer it is written down. That is the difference between a decision and a shrug.
And it is the quiet capstone of a discipline the whole series has run on. Again and again the answer was to defer — the package manager, the general constraint solver, Wasm threads until a program wanted them — always with the same test: is there a real, present need, or a guessed future one? Deciding not to build is not the absence of a decision; done well, it is a measurement, a drafted answer, and a named trigger, which is often more work than saying yes. A language stays small not by refusing features but by making each refusal earn its place as carefully as each feature does.
With that, the concurrency part closes, and with it the last of the major systems. The language has a philosophy, a memory model, effects, four backends held in exact agreement, a compiler written in itself, a public existence, and safe concurrency — and a clear, measured account of where it deliberately stops. What remains is not a system but a horizon. Next, the final part: what comes next — the features on the edge of the map, and the conditions under which they get built.