Fallible main
Every program so far ended with return 0; from func Main() -> int. That made ? useless at the top level: Main could not fail, so a failure had nowhere to go, and every step had to be matched by hand.
Main may also be fallible. Then ? works at the top level too, and a failure ends the program.
Main may fail
The installer in this lesson reserves disk space for two programs. Reserve fails with a SpaceError when there is not enough room, and Main declares the same error:
func Main() -> ! SpaceError {
var free = 100;
PrintLine("Installing the editor (40 MB of {} MB free)", free);
free = Reserve(40, free)?;
Main is now a unit fallible, so it follows the same rules: falling off its end is success, and it has no return 0;. The second reservation asks for 90 MB with only 60 free, so its ? ends the program — the last PrintLine never runs.
How the program ends
The operating system receives a number when a program ends, its exit status. 0 means success; anything else means something went wrong.
Main is declared | and ends by | Exit status |
|---|---|---|
-> int | return n; | n |
-> ! E | reaching its end | 0 |
-> int ! E | return n; | n |
-> ! E or -> int ! E | fail or a failed ? | 1 |
A failure still runs the program's ordinary cleanup on the way out, just as a return would — unlike a panic, which stops at once.
A failure prints nothing
The surprise is what a failure does not do: it prints nothing. The SpaceError value is not shown anywhere, so a user only sees the program stop after the second line.
The exit status is for whoever started the program — a script, a build tool or a shell. In PowerShell, echo $LASTEXITCODE shows it right after the program ends; in a POSIX shell such as bash, it is echo $?. That 1 is the last line of the output below.
If a person should learn why the program stopped, print a message yourself before failing. A catch arm can do both — print, then fail with the same error:
free = Reserve(90, free) catch {
e => {
PrintLine("Not enough space: {} MB needed, {} MB free", e.needed, e.free);
fail e;
}
};
The arm e => ... binds the whole error, and its block ends by leaving, so it never has to produce a value.
The program
The whole lesson is one package in the Examples repository. Its comments explain every step.
// Every program so far ended with `return 0;` from `func Main() -> int`. `Main`
// may also be fallible:
//
// func Main() -> ! SpaceError
//
// Now `?` works at the top level too. Falling off the end of `Main` is
// success, and the program exits with status 0. A failure — from `fail` or
// from a `?` — ends the program with exit status 1.
//
// The surprise is what a failure does *not* do: it prints nothing. The error
// value is not shown anywhere, so a user only sees the program stop. If they
// should learn why, print a message yourself before failing. The exit status
// is for whoever started the program: a script, a build tool or a shell. In
// PowerShell, `echo $LASTEXITCODE` shows it right after the program ends.
//
// This program fails on purpose: the second install does not fit.
import Io::PrintLine;
struct SpaceError {
needed: int;
free: int;
}
func Reserve(needed: int, free: int) -> int ! SpaceError {
if needed > free {
fail SpaceError { needed: needed, free: free };
}
return free - needed;
}
func Main() -> ! SpaceError {
var free = 100;
PrintLine("Installing the editor (40 MB of {} MB free)", free);
free = Reserve(40, free)?;
PrintLine("Installing the compiler (90 MB of {} MB free)", free);
free = Reserve(90, free)?;
// Never printed: the `?` above ended the program with status 1.
PrintLine("Everything is installed, {} MB left", free);
}
Run it
cd Examples/Errors/FallibleMain
rux run
echo $?
The program fails on purpose, so it stops after the second line, and echo $? prints its exit status, 1. In PowerShell, use echo $LASTEXITCODE instead.
Installing the editor (40 MB of 100 MB free)
Installing the compiler (90 MB of 60 MB free)
1
Common mistakes
In
func Main() -> ! SpaceError, return 0; is error: 'return' value must have type '! SpaceError', but found 'int'. Let Main reach its end — or declare -> int ! SpaceError if you want to choose the status yourself.A failed
Main exits with status 1 and prints nothing about the error. Print what the user needs to know before the failure, as the catch above does.Try it yourself
- Change the compiler's 90 MB to 50 and run again. What does
echo $LASTEXITCODEshow now? - Replace the second
?with thecatchabove, so the user learns why the install stopped. - Declare
func Main() -> int ! SpaceError, end it withreturn 2;, and check the exit status when everything fits.
Learn more
- The Main entry point in the Rux Reference
- Unit fallible — the rules a
-> ! Efunction follows - Propagate — the
?thatMaincan now use - Panic — stopping a program that has hit a bug