ADR-343: process::exit(code) Ends main Now, With That Code, the Way main Ends; in a Task It Is Refused

Status: Accepted; D2 and D3 superseded, D1’s mechanism changed by ADR-345 Date: October 13, 2026 Answers: whether a program can end itself with an exit code, what runs when it does, and where it may do so Related: ADR-243 (std::process); ADR-297 D5, D7, D8 (parked cleanup, the drain at program end, cleanup-deadline); ADR-326 D4 (a panic in a task ends the task); ADR-328 (supervisors); Part I 6.4, 7.1, 8; Part III 13.5, 17.1, A.2

1. Question

How does a program end with an exit code other than 0, and what happens to its cleanups and its tasks when it does?

main ends with 0, or with 1 where an error leaves it (Part I 7.1), or by a panic. A command-line tool that reports findings - a linter, a checker - has to end with a code of its own choosing after it has printed them, and nothing in std says how. A function that ends the program at once, from anywhere, is what most languages offer; the question is what it skips and from where it may be called.

2. How others do it

Language Its answer What it costs there Source
Rust std::process::exit ends the process at once; no destructor of any thread runs a BufWriter loses what it held; documented as a trap from memory, not checked
C exit runs atexit handlers and flushes stdio, not the stack another thread’s state is cut off mid-work from memory, not checked
Go os.Exit at once; defers do not run the same as Rust’s from memory, not checked
Java System.exit, Python sys.exit Java runs shutdown hooks; Python raises SystemExit, so finally runs Python’s is caught by a bare except from memory, not checked
Erlang a process cannot end the node; init:stop shuts it down in order the orderly path is the only one from memory, not checked

3. Options, weighed

Option Why it lost, or won
A As Rust: end the process at once, no cleanup a buffered file loses its tail, a transaction is not rolled back; Part I 6.4 promises a value’s cleanup runs, and this would be the one way around it
B exit callable from a task too a task’s exit ends the program while other tasks are mid-work; their cleanups are cut off, and A.2 already says a task’s failure ends only the task
C No function; main returns its code (fn main() -> i64) a tool that finds its answer deep in a call has to carry it back through every frame by hand; the owner holds a function to end with a code indispensable
D A function that ends main with the code on main’s own ordered path, refused wherever it would run in a task chosen

4. Decision

D1 — process::exit(code) ends main now, with code, on the path a normal end of main takes.

Changed by ADR-345 D3, D4: the ordered end stands; exit reaches it as the fatal error process::Exit in the error channel, not by an unwinding of its own.

The call does not return. What main holds and every caller between main and the call holds dies as at a return from main: each value’s cleanup runs (Part I 6.4), in the order the values die. Then the program ends as when main returns: the tasks it started and nobody joined run to their end, the parked cleanups run, both under cleanup-deadline (ADR-297 D5); then the process ends with code. A deadline that expires is exit status 70, as without exit (ADR-297 D8). The panic hook does not run: an exit is not a panic.

D2 — exit is a derived property of a function, in the ledger.

Superseded by ADR-345 D3: exit throws; the ledger has no exits column.

A function that calls process::exit, or calls a function that may, may end the program. The compiler derives it over the call graph, as it derives throws (Part III 13.5), and writes it to the ledger as exits = true. A function value whose function type has it carries it with the value.

D3 — Code that may end the program is refused where it runs in a task.

Superseded by ADR-345 D5: in a task Exit is an error that ends the task; NK2607 is retired.

spawn, a branch of overlap, a lambda of a parallel chain (par_iter), a child of a supervisor: a call that may end the program there, directly or over any depth of calls, or a function value that may, is NK2607. The message says the way out: throw an error, or return a result, and end in main.

D4 — The code is an i64 from 0 to 255.

A value outside is an unrecoverable error (Part III A.2). A constant outside is refused at compile time, as NK1116 refuses a constant that does not fit.

5. Reasons

6. Consequences

7. What this does not decide

This site is open source. Improve this page.