Errors · Lesson 9.12

Fallible main

Source
Declare func Main() -> ! E so ? works at the top level, and see that a failure ends the program with exit status 1 without printing anything.
You'll need: Unit fallible, Propagate

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 declaredand ends byExit status
-> intreturn n;n
-> ! Ereaching its end0
-> int ! Ereturn n;n
-> ! E or -> int ! Efail 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.

Src/Main.rux
// 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

Try it yourself

  1. Change the compiler's 90 MB to 50 and run again. What does echo $LASTEXITCODE show now?
  2. Replace the second ? with the catch above, so the user learns why the install stopped.
  3. Declare func Main() -> int ! SpaceError, end it with return 2;, and check the exit status when everything fits.

Learn more