Unit fallible
Some operations have no answer to give back. When a withdrawal works, the money is gone from the account and there is nothing more to say. But it can still fail, and the caller still needs to know.
Such a function is written -> ! E: an error type after the !, and nothing before it.
! E is () ! E
! E is short for () ! E. The () is the unit — the empty tuple, whose only value is also written (). It is a success that carries no information beyond "it worked". Both spellings compile and mean the same type; the short one is what you will see everywhere:
func Withdraw(account: &var Account, amount: int) -> ! ShortOfFunds {
The account is a mutable reference, so the function changes the caller's account in place. The only thing left to report is whether that happened.
Three ways out
Because there is no value to return, the function succeeds simply by reaching its end. A bare return; succeeds early, and only fail makes it fail:
if amount == 0 {
// Nothing to do, and that counts as success.
return;
}
if amount > account.balance {
fail ShortOfFunds { missing: amount - account.balance };
}
account.balance -= amount;
// Falling off the end: success.
| The function… | The outcome |
|---|---|
| reaches its closing brace | success |
runs return; | success, early |
runs fail error; | failure, carrying error |
Matching a unit success
The success channel still holds a value — the unit — so its pattern has parentheses with () inside:
match Withdraw(account, amount) {
.Success(()) => PrintLine("take {}: done, {} left", amount, account.balance),
.Failure(error) => PrintLine("take {}: refused, {} short", amount, error.missing)
}
.Success(_) works too, since _ matches any value, the unit included. What does not work is leaving the parentheses out: .Success alone is a pattern with no payload, and the success channel always has one.
The output follows the balance as it changes. Taking 0 succeeds without touching anything, 80 is refused while 60 is left, and the next 60 empties the account — so even 1 is refused after that.
The program
The whole lesson is one package in the Examples repository. Its comments explain every step.
// Some operations have no answer to give back. When a withdrawal works, the money is gone from
// the account and there is nothing more to say. Such a function is written `-> ! E`: an error
// type after the `!`, and nothing before it.
//
// `! E` is short for `() ! E`. The `()` is the unit, the empty tuple, whose only value is also
// `()` — a success that carries no information beyond "it worked". Because there is no value to
// return, the function succeeds simply by reaching its end. A bare `return;` succeeds early, and
// only `fail` makes it fail.
import Io::PrintLine;
struct Account {
balance: int;
}
struct ShortOfFunds {
missing: int;
}
// Changes the account in place; the only thing to report is whether that happened.
func Withdraw(account: &var Account, amount: int) -> ! ShortOfFunds {
if amount == 0 {
// Nothing to do, and that counts as success.
return;
}
if amount > account.balance {
fail ShortOfFunds { missing: amount - account.balance };
}
account.balance -= amount;
// Falling off the end: success.
}
func Attempt(account: &var Account, amount: int) {
// The success pattern holds the unit, so it is written `.Success(())`.
match Withdraw(account, amount) {
.Success(()) => PrintLine("take {}: done, {} left", amount, account.balance),
.Failure(error) => PrintLine("take {}: refused, {} short", amount, error.missing)
}
}
func Main() -> int {
var account = Account { balance: 100 };
Attempt(account, 40);
Attempt(account, 0);
Attempt(account, 80);
Attempt(account, 60);
Attempt(account, 1);
return 0;
}
Run it
cd Examples/Errors/UnitFallible
rux run
take 40: done, 60 left
take 0: done, 60 left
take 80: refused, 20 short
take 60: done, 0 left
take 1: refused, 1 short
Common mistakes
return 0; inside Withdraw is error: 'return' value must have type '! ShortOfFunds', but found 'int'. There is no success value to give; write return; or let the function reach its end..Success => ... is refused with error: pattern '.Success' expects 1 field, but found 0. Write .Success(()) or .Success(_).Withdraw(account, 40); on its own is error: fallible result of type '! ShortOfFunds' is discarded. No value comes back, but the failure still does. Discard explains how to ignore it on purpose.Try it yourself
- Change the signature to the long spelling
-> () ! ShortOfFundsand check that the program still compiles and prints the same. - Add
Deposit(account: &var Account, amount: int) -> ! TooMuchthat refuses when the balance would pass 1000. - Replace
.Success(())with.Success(_). Does anything change?
Learn more
- Fail — the failure channel's
return - Discard — the deliberate way to ignore a
! Eresult - Tuple — the empty tuple
()is the unit - Mutable reference — how
Withdrawchanges the caller's account