Status: Superseded by ADR-292 — its D1-D3 are D18-D20 there
Date: September 20, 2026
Target Version: Nikaia 0.0.82
Related: ADR-163 D3 (the defect this closes),
ADR-115 D4 (the written example that did not lower, and whose
secondary list this makes room for), ADR-050 D5 (the first
failure in written order wins), ADR-148 D1 (select),
ADR-157 D2 (the envelope is opened once at the binding),
ADR-159 D2, ADR-160 D1 (the other two channels),
Part III C.1, Part I 7.1 and 8.1.2
ADR-163 D3 named a defect rather than fixing it:
overlap { … } catch { … } does not lower. It is
ADR-115 D4’s own written example — the form that record uses
to say how a block combines what its branches failed with — and the reason to
leave it there was that fixing it is that record’s own first step.
This is that step, and measuring it turned one defect into four. All four are
Part III C.1 — rustc speaking about a file the
author never wrote — and two of them need no joining block at all to reach.
std.nikaia_std::task::combine<n> takes the branches’ results in written order
and answers the first Err among them, which is
ADR-050 D5 written as the language below’s own control flow. The
block’s value is that one Result.
It was a ? per branch at the call before, and a ? leaves the function —
so where a catch on the block was what handled the failure, the handler got a
match over the vehicle’s tuple as though it were a Result. Where the failure
does leave the function, the ? is now one, on the block.
One function that sees every outcome is the point beyond the fix.
ADR-115 keeps the later failures as the winner’s secondary, and
combine<n> is the only code that ever holds them all. Writing the combination
at the call site and again inside that record would be writing the same loop
twice.
A select’s arms are the same rule in the shape that construct has: where the
handler is on the block, the winning arm takes its own failure apart and its
value becomes the Ok half, because a ? there would leave the function too.
The enclosing function may not be throws at all — overlap { … } catch { … }
is the shape where it usually is not — so its channel is the box, and
match error { LoadError::Missing(w) => … }, which is Part I 7.1’s whole point,
does not compile over one.
A joining block therefore has an error set of its own: the union of its
branches’, in the ledger’s own order. The channel that set travels in is the
same derivation a function’s key gets — Thrown<E>, a library’s type bare,
a generated sum, or the box — asked of the set instead of of the key. Both the
Ok the branches are wrapped in and what the handler binds read it, so the two
cannot disagree.
It resolves no more than was already resolved here (ADR-011
D2): a branch’s callee by the name the source wrote, and that callee’s throws
column off a ledger — which is exactly what the catch already did to decide
whether to open an envelope. A branch whose callee no ledger knows contributes
the absence of a claim, which makes the set the box: the safe direction.
rustc says nothing about the file it was handed.Two warnings were found by compiling the shapes above, and neither needs a joining block to reach:
catch { println(f"{error}") } over a named channel was unused_braces. The
envelope was opened as a block wrapped around the handler; it is the
handler’s first statement now, which is one block instead of two.fn f() -> String throws { throw E() } was unreachable call: the tail of a
throws body is wrapped in Ok(, and a throw writes its own Err(…) and
leaves. A tail that is a throw is not wrapped. A tail return is the other
way round and stays wrapped — it is rewritten to its bare value there, and the
Ok( is what makes that value the function’s outcome.A warning about a generated file is C.1 one severity
down, and it is held by a test that asserts rustc’s stderr is empty rather
than that it succeeded.
secondary list
now has one function to live in rather than a loop to be written first.combine<n> in std, a flag on the flow that is set
on the one expression a catch is written around, and the channel derivation
factored to take a set.secondary list itself, which is ADR-115’s and is not
built. combine<n> drops every failure but the first, exactly as the ?s did.docs/open-decisions.md now carries, and it is the one thing between this
record and that one.All three decisions. crates/nikaia/tests/joining.rs is eleven tests, most of
which compile and run the result: the handler on an overlap and on a
select, the handler matching the branches’ variants with the envelope
opened once, and one test that asserts rustc said nothing about either of
the two programs that used to make it warn.