process::exit(code) Ends main Now, With That Code, the Way main Ends; in a Task It Is RefusedStatus: 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
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.
| 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 |
| 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 |
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.
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.
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.
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.
Cleanup conditional on nobody calling exit.
Ending main is already defined, including what happens to tasks; exit is
that end, reached early.main
decides that the program ends, because only main’s end waits for the
others. The refusal is at compile time because the property is known there,
and a run-time refusal would turn a design choice into a crash.256 would read as success.1 after its findings with one call, and its buffered
output is flushed.exit cannot be used from a task, and the
ledger tells a consumer so before it builds.main’s frames is the mechanism; a target
that traps rather than unwinds (A.2) ends at once, as a panic there does.SIGTERM) and how it reaches main.main to end; today it returns or throws.