Status: Superseded by ADR-280 — its D1-D2 are D13-D14 there, D3 is in D10 Date: September 27, 2026 Target Version: Nikaia 0.0.228 Answers: #199, whether the list survives a hop to a caller with a bare channel of its own — ADR-170 D1’s open question Supersedes: ADR-159 D2 (a library’s error travels bare) Refines: ADR-170 D1 (the envelope is no longer the joining body’s alone) Related: ADR-240 D1 (the envelope keeps its site and list in the box), ADR-160 D1 (the generated sum), ADR-082 D1 (a grammar’s entry rule)
ADR-159 D2 sent a library’s error bare — a function that reads a file had
Result<T, io::IoError> — because no throw of the program raised it and an
envelope’s first job was the site. ADR-170 D1 put the envelope back where a body
joins, because an overlap that combines failures has a list to carry, and
left the next question open: what a caller that only propagates such a failure
does with it.
Asked by running it, the answer was worse than the list is lost. pair joins
two reads and has Thrown<io::IoError>; relay returns pair() and had the
bare io::IoError; nothing converts the first into the second, and rustc
refused the file (From<Thrown<IoError>> is not implemented for IoError) — a
correct program refused in a file nobody wrote. A generated sum had the same
hole: its member for a library’s error was the bare type.
#199 said the transitive version was “a derived column like locks,
and worth its own measurement rather than a guess”. Before a column, the
simpler rule was measured.
benches/envelope: three calls deep, each ?-propagating, 5 million calls,
best of seven, three runs.
| row | ns/call | |
|---|---|---|
| bare, success | 2.10 – 2.69 | |
| envelope, success | 2.06 – 2.56 | ×0.95 – ×0.98 |
| bare, failure | 13.17 – 14.39 | |
| envelope, failure | 13.36 – 14.88 | ×0.98 – ×1.03 |
| bare, success, twice (control) | 2.11 – 2.64 | ties |
Result<i64, io::IoError> is 32 bytes and Result<i64, Thrown<io::IoError>>
40: the envelope is one word. Neither path pays for it in time beyond the
control’s own spread.
Thrown<E>, everywhere.Not only where a body joins: every function of the program whose failure is one
library error type has Thrown<E>, and a generated sum’s member for one is
Thrown<E> as the program’s own types’ members are. So a propagating caller
has the channel its callee has, and the list goes with the failure through any
number of callers — with no column to derive, because there is no condition.
A ? from a std call, which hands the error bare, puts the envelope on
through From<E> for Thrown<E> — with no site, which the long form says in
ADR-159 D3’s own sentence. A sum gets the same From<E> for each library
member. A grammar’s entry rule is the one call of this program whose body
is not written as a function here, and it hands its parse error bare, as std
does.
throw of a library’s error has a site.ADR-159 D2’s reason — no throw here raised it — is false where one did:
throw io::IoError::NotFound(p) is lowered as a program’s own error is,
throwing(error, site).
catch opens the envelope, and error.full() includes the list.A handler over such a channel takes the envelope apart at the binding, as it
does for a program’s own type (ADR-157 D2): match error names
the library’s variants, {error} is its message, and throw error puts it
back with what joined it. error.full() there is built from the two halves
the handler holds, and it now includes the failures that joined — which it had
left out for a program’s own errors as well.
locks is derived. It answers the same question
with a fixpoint over the call graph, a second rule for sums, and a failure
mode where the column is wrong; the measurement says the uniform rule costs
nothing to be right about.From<Thrown<E>> for E for std’s
error types. It compiles and drops the list, which is the thing the hop was
asked to keep; and a described crate’s error type is not std’s to write it
for.