ADR-351: A Function Value Keeps the Error Set Inferred for It

Status: Accepted Date: October 14, 2026 Answers: what a call through a function value can fail with, when the value was stored, handed on or kept rather than called at once Related: ADR-023 (throws names no types; the set is inferred into the ledger); ADR-280 D15 (a set of two or more is one generated sum); ADR-288 D31 (sync(f): a property the lambda decides); ADR-345 D2 (a fatal error is taken only by name); ADR-346 D5, D6, D7 (a task’s errors reach its joiner; detached errors; supervisors); Part I 5.4 C, 7.1; Part II 12.8; Part III 13.5, 17.1

1. Question

What can job.run() fail with, where run is a field holding a function?

throws names no types: what a function fails with is inferred from its body over the whole program (Part I 7.1), and a catch sees the sum of what can arrive. A function value handed to a parameter that only calls it counts with that set for the callee. One that is kept, in a field, a list, a supervisor::Child or a server’s handler, has so far followed its type alone, fn() throws: can fail with something. The set the compiler inferred was not carried with the value, so a catch over a call through it cannot be exhaustive, a fatal error in it cannot be seen before the program runs, and a supervisor hands a child’s error on wrapped (ADR-346 D7) because the child’s set is unknown.

2. How others do it

All rows are from memory, not checked against a current manual.

Language A function value’s errors A stored function
Java checked exceptions written by hand; java.util.function declares none lambdas that throw are wrapped in unchecked exceptions; a generic throws E carries one type
Swift rethrows for a parameter called at once; typed throws throws(E) is generic throws(E) is part of the closure’s type and stays with it
Rust the error is in the result type, Fn() -> Result<T, E> the type keeps E; mixed closures in one list need one E
Koka effect rows, inferred and polymorphic a stored function keeps its row; a list of them has the union
Kotlin, C# unchecked; nothing in the type nothing to keep

Java’s failure is the cost of writing the sets by hand. Where the sets are inferred (Koka) or generic (Swift, Rust), a stored function keeps its errors.

3. Options, weighed

Option Why it lost, or won
A A kept function fails with something (?), as before no catch over it is exhaustive and no fatal error in it is visible to the compiler; the run time has to recover at the end what the compiler knew and dropped
B A function value carries its inferred set wherever it goes chosen (D1-D7)
C Only std’s types carry it, case by case two rules for one thing; a program’s own struct Job { run: fn() throws } stays unprovable, and each new std type that keeps a function repeats the work

4. Decision

D1 — A function value carries the error set inferred for it.

A named function carries the set inferred for its body; a lambda the set of its body. The set travels with the value through a let, an assignment, a field, an element of a list or a map, a result and a parameter, whether the receiving function calls the value at once or keeps it. fn() throws in the source still names no types; the set is the compiler’s, as for a function (ADR-023).

D2 — Calling a function value fails with its set; making or moving one fails with nothing.

A call through a function value can fail with exactly the set the value carries, and a catch over it sees that set’s sum (Part I 7.1). Writing a function value, storing it, handing it on or returning it calls nothing and can fail with nothing.

D3 — Where function values meet, the value carries the union.

Where values with different sets meet in one place, the place carries the union of their sets: the arms of an if or a match, the elements of a list or a map, the values assigned to one variable or field, the arguments handed to one kept parameter by every caller. A code parameter the body does not keep is from(p), replaced at each call by what that call hands it (D4). A place no failing value reaches carries the empty set, and a call through it cannot fail.

Changed by ADR-error-sets-1 D1, D2.

D4 — A ledger says what a call through a given function throws.

A function whose body calls a function value it was given says so in its throws as from(p), where p is the parameter or a field path of it (from(handler), from(job.run)). A caller replaces from(p) with the set of what it passed. A type that holds a function value carries that value’s set as part of the type of each value of it, as TaskHandle[T] carries its body’s (ADR-346 D5).

D5 — supervisor::run throws its children’s fatal errors themselves.

run throws supervisor::Escalated and the fatal members of its children’s sets. A child that ends with a fatal error is not restarted (ADR-346 D7); the supervisor stops every child and throws that error, which a catch takes by naming its type (ADR-345 D2). supervisor::Handed is withdrawn.

D6 — A server’s handler fails as a detached task does.

An error a net::serve handler ends with is the error of a detached task (ADR-346 D6): printed in the long form, and a fatal one replaces the exit status at main’s end. Which handler errors are fatal is known from the handler’s set.

D7 — "?" stays for what no ledger describes.

A set member "?" still means this compiler cannot name it: a call into code no ledger describes. A function value is never a reason for it.

D8 — Kept code lives in a value something holds; its set belongs to that storage, and the ledger says what a function puts there.

There is no global mutable data (ADR-281 D32) and no trait object (a trait is not a type, Part I), so code a function keeps is always reached from one of its parameters or its result: a registry is a value its caller holds.

struct Registry { tasks: Vec[Task] }
impl Registry {
    fn register(ref mut self, t: Task) { self.tasks.push(t) }   // code: self.tasks.run: from(t)
    fn fire(ref self) throws { for t in self.tasks { t.run() } } // throws: from(self.tasks.run)
}

5. Reasons

6. Consequences

7. What this does not decide

This site is open source. Improve this page.