Errors · Lesson 9.5

Discard

Source
See why a fallible result cannot be silently ignored, and how to discard one on purpose.
You'll need: Unit fallible, Outcome

A fallible result cannot be ignored. Calling a fallible function as a bare statement, with nothing looking at what it returns, is a compile error — otherwise a failure could vanish without anyone noticing.

Sometimes ignoring it is exactly right, though. A cash machine whose receipt printer has run out of paper should still hand over the money. This lesson shows why the compiler is strict and how to tell it, in the code, that you mean it.

A bare call is refused

PrintReceipt is a unit fallible: it either printed the receipt or it did not. Call it the way you call PrintLine and the compiler stops you:

PrintReceipt(printer, amount);
error: fallible result of type '! OutOfPaper' is discarded
  note: a failure that nothing handles is lost
  help: propagate it with '?', recover with 'catch', or match both '.Success' and '.Failure'

Binding the result to _ does not help: let _ = PrintReceipt(printer, amount); is refused the same way, with the note binding a fallible to '_' does not handle its failure. A _ only throws the value away; it does nothing about the failure inside it.

Discarding on purpose

The deliberate discard is one phrase:

PrintReceipt(printer, amount) catch { else => {} };

It says "I know this can fail, and it does not matter here". catch is the subject of the next lesson; for now, read the line as that one phrase. The third withdrawal in the output finds the paper gone, hands over the money anyway, and prints no receipt.

Only for a unit success

The empty block {} stands in for the missing success, and an empty block has no value — it completes with (). So this phrase works only for a ! E function. One that succeeds with a value, such as ExactDivide from Fallible, is discarded with a match that names both channels and does nothing in either:

match ExactDivide(12, 4) {
    .Success(_) => {},
    .Failure(_) => {}
}
What you writeResult
PrintReceipt(printer, amount);error: the result is discarded
let _ = PrintReceipt(printer, amount);error: binding to _ handles nothing
let r = PrintReceipt(printer, amount);a warning if r is never read
PrintReceipt(printer, amount) catch { else => {} };a deliberate discard of a ! E
a match with .Success(_) => {} and .Failure(_) => {}a deliberate discard of any fallible

Why PrintLine was never a problem

PrintLine is not fallible. It returns an optional, IoError?, saying whether the console write had trouble — and an optional may be ignored. That is why every lesson so far could call it as a bare statement.

The program

The whole lesson is one package in the Examples repository. Its comments explain every step.

Src/Main.rux
// A fallible result cannot be ignored. Calling a fallible function as a bare statement, with
// nothing looking at what it returns, is a compile error — otherwise a failure could vanish
// without anyone noticing.
//
// Sometimes ignoring it is exactly right, though. A cash machine whose receipt printer has run
// out of paper should still hand over the money. For that there is a deliberate discard:
// `F() catch { else => {} };`. It says, in the code, "I know this can fail and it does not
// matter here". `catch` is the subject of a later lesson; for now, read it as that one phrase.
import Io::PrintLine;

struct Printer {
    paper: int;
}

struct OutOfPaper {
}

// A unit fallible: it either printed the receipt or it did not.
func PrintReceipt(printer: &var Printer, amount: int) -> ! OutOfPaper {
    if printer.paper == 0 {
        fail OutOfPaper {};
    }
    printer.paper -= 1;
    PrintLine("    receipt: {} taken", amount);
}

func Withdraw(printer: &var Printer, amount: int) {
    PrintLine("hand over {}", amount);

    // The line below does not compile:
    //
    //     PrintReceipt(printer, amount);
    //
    // error: fallible result of type '! OutOfPaper' is discarded
    //   note: a failure that nothing handles is lost
    //   help: propagate it with '?', recover with 'catch', or match both '.Success' and '.Failure'
    //
    // Writing `let _ = PrintReceipt(printer, amount);` is refused the same way: binding a
    // fallible to `_` does not handle its failure either.

    // The deliberate discard: a missing receipt is not worth stopping for. The empty block `{}`
    // stands in for the missing success, so this works only for a `! E` function. One that
    // succeeds with a value, such as an `int`, is discarded with a match whose arms are
    // `.Success(_) => {}` and `.Failure(_) => {}`.
    PrintReceipt(printer, amount) catch { else => {} };
}

func Main() -> int {
    var printer = Printer { paper: 2 };
    Withdraw(printer, 20);
    Withdraw(printer, 50);
    Withdraw(printer, 10);

    // `PrintLine` itself is not fallible. It returns an optional, `IoError?`, saying whether
    // the console write had trouble, and an optional may be ignored — which is why every lesson
    // so far could call it as a bare statement.
    return 0;
}

Run it

cd Examples/Errors/Discard
rux run
hand over 20
    receipt: 20 taken
hand over 50
    receipt: 50 taken
hand over 10

Common mistakes

Hiding the result behind _.
let _ = PrintReceipt(printer, amount); is error: fallible result of type '! OutOfPaper' is discarded, with the note binding a fallible to '_' does not handle its failure. Write the catch { else => {} } phrase instead.
The unit phrase on a fallible with a value.
On an int ! E result, catch { else => {} } is error: a block arm completes with '()', but 'catch' must recover a value of type 'int'. Use the two-arm match above.
Storing the result and forgetting it.
let r = ExactDivide(12, 4); with r never read compiles, but warns: fallible local 'r' is never read; its failure is never handled. Take the warning seriously — the failure is just as lost.

Try it yourself

  1. Start the printer with no paper at all. What does the output look like?
  2. Replace the discard with a match that prints " no receipt: out of paper" on failure.
  3. Write the bare call PrintReceipt(printer, amount); back in and read all three lines of the error.

Learn more

  • Catch — the full form of catch, with one arm per error
  • Catch fallback — catch { else => ... } with a real fallback value
  • Optional — the type PrintLine returns, which may be ignored