catch That Ignores the Error Binds _errorStatus: Superseded by ADR-308 — its D1-D5 are D1-D5 there
Date: September 15, 2026
Target Version: Nikaia 0.0.8
Related: ADR-296 D17 (the emitter writes a name for a name, and
this is a name it writes for no name at all), ADR-292 (the catch
this lowers), ADR-295 D3 (the warning is rustc reading the
emitted code correctly, so the emitted code is what changes — not an allow)
fn energy(text: &str) -> i64 {
return text.len() as i64 catch { 1000 }
}
warning: unused variable: `error`
--> n-body.rs:104:13
|
104 | Err(error) => { 1000 },
The author wrote catch { 1000 }. There is no error in their program. Kap 7.1
says a handler sees the failure under that name, so the match arm binds it
whether or not the handler reads one — and the shape that ignores it is the
common one, the one Part I 7.1 teaches first.
That is Part III C.1’s class one severity down: a message reaching the user in the backend’s words, about a file nobody wrote, naming a binding that exists nowhere in the source. It is the second half of what ADR-295’s survey turned up.
It is not the same as the two beside it. The same survey found
unused variable: x and unused variable: k in tests/samples/, and those are
names the user wrote — a let and a for binding. The message is actionable
and only the file it names is wrong, which is a smaller and different problem.
This one is about something nobody wrote at all.
error binds _error.The name is still there — Kap 7.1’s promise is unchanged, and a handler that reads the failure gets exactly what it got before. What changes is the arm written under a handler that does not, and with it the only message this ever produced.
contracts::order::names_in_block collects every name a block
mentions and is used for the ordering analysis; block_mentions is a public
question over it. No second walk, and no new notion of “is this name read” —
which matters, because a second notion is a second thing that can disagree.
The walk counts a word inside an interpolated string’s raw
text, so f"{error}" is a mention; it would also count the word error in a
comment-like position, and that is fine. The two failure modes are not
symmetric:
| the walk says | the truth | what is written | what happens |
|---|---|---|---|
| mentioned | mentioned | error |
correct |
| mentioned | not mentioned | error |
yesterday’s warning, on a shape that used to get it anyway |
| not mentioned | not mentioned | _error |
correct |
| not mentioned | mentioned | _error |
a program that does not compile |
Only the last row is a new failure, and it is the one an over-approximate walk cannot reach. A precise analysis would have to be right; this one only has to lean.
emit::CAUGHT is the spelling the emitter writes and the spelling the walk is asked about. Two spellings would be a
binding that goes unread while the walk looks for the other — the bug this
closes, arrived at from the other side.
allow.ADR-295 D3’s reason, unchanged: the
warning is rustc reading the emitted code correctly. Silencing it would leave
the emitted code saying something it does not mean, and would silence the day it
is about something real.
examples/n-body/src/main.nika builds without the warning, and so does every program
whose catch supplies a constant. The emitted arm is one character wider in
that case and identical otherwise; nothing about what a program means changes,
and no diagnostic is added — the fix is the absence of one.
The cost is one walk of the handler’s block per catch, at lowering time, over
a structure already in memory. It is the same walk the ordering analysis runs
over the same blocks.
#221’s entry closes — a catch that ignores the error warns
about a name the emitter wrote, cited by subject because that page’s numbers
move — with all three polarities its closure statement named checked: a hole, a
nested block, and a handler that only passes error on.
error is the right name. Kap 7.1 chose it and this keeps it.
D4 makes changing it a one-line change, which is the most this owes the
question.unused variable warnings about names the user did write. Those
stay: the message is actionable, and what is wrong is the file it points at —
which is ADR-300’s problem, not this one.catch on an infallible expression. text.len() as i64 catch { 1000 }
lowers to a match over something that is not a Result and gets E0308
about the generated file. That is the same class and a different defect; it is
filed rather than fixed here.contracts::order::block_mentions, emit::CAUGHT, and the one match in the
Expr::TryCatch arm that chooses between them. Exercised by
crates/nikaia/tests/catch_handler.rs: a table of four handler shapes against
the arm each must produce, a nested block, the n-body line compiled by rustc
with its stderr read for the word unused, and a handler that reads the error
compiled and run, so that the _error direction is checked by a program
that would not build if it were taken wrongly.