Part 9: Errors
An optional can say that a value is missing, but never why. This part gives a failure a voice. A fallible T ! E is an answer of type T or an error of type E saying what went wrong, and the compiler makes sure no failure is ever dropped without the code saying so. By the end of the part you can write functions that fail precisely, decide at each step whether to handle a failure or pass it on, and tell a failure the caller can fix from a bug that should stop the program.
What you will learn
- Declaring a fallible with
T ! E, or! Ewhen there is no answer, and failing withfail. - Opening an outcome with
.Successand.Failure, and why a fallible result cannot be ignored. - Recovering with
catch— one arm per case, or oneelsefallback for all. - Passing a failure to the caller with
?, and adding context on the way with? else (e => ...). - Designing error types: a variant of cases, or a sum of unrelated errors
A | B. - A fallible
Main, turning absence into an error with?? fail, and nested fallibles. Panic,AssertandDebugAssertfor the situations only a bug can reach.
Which error tool?
Most of this part is choosing the right tool for one moment in the code. The questions run in this order:
flowchart TD
start(["Something can go wrong"]) --> bug{"Can it happen in a<br/>program with no bugs?"}
bug -- "no, only a bug" --> stop["Panic, Assert, DebugAssert<br/>9.15–9.16"]
bug -- "yes" --> why{"Does the caller need<br/>to know why?"}
why -- "no" --> opt["An optional T?<br/>Part 8"]
why -- "yes" --> fal["A fallible T ! E, or ! E<br/>9.1–9.3"]
fal --> who{"Who decides what<br/>the failure means?"}
who -- "this function" --> reason{"Does the reason<br/>matter here?"}
reason -- "yes" --> arms["match, or catch with<br/>one arm per case<br/>9.4, 9.6"]
reason -- "no" --> fallback["catch { else => fallback }<br/>or a deliberate discard<br/>9.5, 9.7"]
who -- "the caller" --> same{"Does the error already<br/>fit the caller's type?"}
same -- "yes" --> q["? passes it on<br/>9.8, 9.11"]
same -- "no, or context to add" --> mapped["? else (e => ...)<br/>9.10"]Lessons
| Lesson | What you will learn | |
|---|---|---|
| 9.1 | Fallible | return either a value or an error with T ! E |
| 9.2 | Fail | report a failure with fail |
| 9.3 | Unit fallible | a function that returns nothing but can still fail: ! E |
| 9.4 | Outcome | match a result as .Success or .Failure |
| 9.5 | Discard | why the compiler refuses to let a result be ignored, and how to discard one on purpose |
| 9.6 | Catch | handle the ways an operation can fail with catch |
| 9.7 | Catch fallback | turn any failure into a default value with catch { else => ... } |
| 9.8 | Propagate | hand a failure straight to the caller with ? instead of matching it |
| 9.9 | Error variant | describe the ways an operation can fail with a variant |
| 9.10 | Error mapping | add context to an error as it passes through with ? else (e => ...) |
| 9.11 | Error sum | fail in more than one way with an error sum A | B |
| 9.12 | Fallible main | let Main itself fail, and see the exit status |
| 9.13 | Absence to error | turn a missing value into a failure with ?? fail |
| 9.14 | Nested fallible | results inside results: T? ! E and (T ! E1) ! E2 |
| 9.15 | Panic | stop the program when something impossible happens |
| 9.16 | Assert | check an assumption while the program runs |
Before you start
Finish Part 8: Optionals first — fallibles are built on the same ideas, and several lessons compare the two side by side. You also need variants from Part 6 and the patterns of Part 7. Each lesson's package is in the Examples repository's Errors/ folder:
cd Examples/Errors/Fallible
rux run
After this part
Part 10: Sum types takes the A | B you met in Error sum and makes it a type of its own, usable anywhere. Before moving on, try the checkpoint project Calculator: it evaluates sums and key-press tapes where every way a calculation can fail is a case of an error variant.
For the rules behind this part, see Error handling and Fatal errors in the Rux Reference, and Panic and Assert in the API reference.