ADR-090: A catch That Ignores the Error Binds _error

Status: 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)

1. Context

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.

2. Decisions

D1 — A handler that does not mention 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.

D2 — The question is asked of the handler’s block, by the walk that already exists.

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.

D3 — The over-approximation is the safe direction, and that is why this is answerable at all.

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.

D4 — The name lives in one place.

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.

D5 — Not an 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.

3. Consequences

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.

4. What this does not decide

5. What is built

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.

This site is open source. Improve this page.