ADR-170: An Error Carries the Failures That Joined It, and a Body That Joins Puts an Envelope on a Bare Channel

Status: Superseded by ADR-280 — its D2 is D19, D3 is in D20 there; D1, generalized by ADR-241 D1, is contained in D13 Date: September 20, 2026 Target Version: Nikaia 0.0.89 Related: ADR-115 D1 and D2 (the decision this builds), D3 and D4 (what is not built yet), ADR-050 D5 (the first failure in written order wins), ADR-159 D2 and D3 (a library’s error travels bare, and what the long form says when there is no site), ADR-164 D1 and D2 (combine<n>, and one channel per block), ADR-023 D6 (an error knows where it was raised), ADR-157 D2 (the envelope is opened once at the binding), Part I 8.1.2

1. Context

ADR-115 D1 gives every error a secondary list — the failures that joined it — and D2 fills it from an overlap: the first failing branch in written order is the block’s error, and every later one joins its list. Its context is three loads, two of them failing on one outage, and a log that shows one.

Nothing about the list was hard until the channel got a type. When that record was written every failure travelled in one box, and this compiler’s own envelope had room for anything. Four records later the channel is one of four things, and two of them had nowhere to put a list: a library’s error travels bare (ADR-159 D2), and a generated sum’s members each keep their own. Those two are the common ones — a function that only reads files has io::IoError as its whole set, which is exactly this record’s own worked case.

docs/open-decisions.md carried the question. The owner answered A, and added that it was always the plan and only badly documented — which is the reading this record takes: D2’s reason was about the site, never about the list.

2. Decisions

D1 — Where a function’s body joins, a bare channel is an envelope.

ADR-159 D2 sends a library’s error bare because there is no throw in this program to have a site. That holds, and it is about the site alone: an overlap that combines failures is the language doing something, so there is something to attach even where nothing was raised here.

So a function whose body holds a joining block gets Thrown<E> where it would otherwise have had E, and a ? from a callee with a bare one converts through From<E> for Thrown<E>. The envelope it puts on says no site recorded — ADR-159 D3’s own sentence, which is what keeps D2 true in the part that was ever about the site.

The body it is written in and no further. A caller that propagates such a failure has a channel of its own, and whether the list survives that hop is a question this does not answer; #199 carries it. What this covers is the block, its catch, and the function around them — which is where a handler for it is written and where an operator reads it.

D2 — The list is of the channel’s own type, and each entry keeps its envelope.

A joining block hands every branch the same channel (ADR-164 D2), so the failures that meet there are the same kind of thing as the one they meet: Thrown<E> carries Vec<Thrown<E>>. Each keeps its own envelope, so each keeps the site ADR-023 D6 gives it — and a secondary with secondaries of its own is D3’s tree, with nothing further to build for the shape.

The boxed channel joins through a downcast, which is the price of the box rather than a shortcut. A failure that joins an error from below Nikaia — one with no envelope at all — is dropped, and that is the one case the list cannot cover. It is named here rather than discovered.

D3 — An uncaught failure prints the list, indented, and the trace note is said once.

A main that hands back an Err is printed by the language below through Debug, so that is the operator’s view of a program that stopped — and a short form there would be the one place the joined failures are dropped on the floor. It is the long form now, with each joined failure under the one it joined and every line of it moved right, so the depth on the page is the depth in the list.

The no trace note is said once, on the primary: it is about how this process was started, and repeating it under every failure says one thing three times and buries them.

3. Consequences

4. What this does not decide

5. What is built

D1, D2 and D3. crates/nikaia-std’s own tests hold the carrier: what joined printed under it in join order, the trace note said once, an enveloped error from below still saying no site recorded, and the list surviving throw error. crates/nikaia/tests/joining.rs holds the end of it — two failing branches, compiled and run, with the first in written order winning and the second printed under it. That test was written in ADR-163’s package asserting the second failure was nowhere, and turning it over is what building this meant.

This site is open source. Improve this page.