Absence to error
An optional says "nothing here" and stops there. PriceOf(7) returns none, and none cannot say which code was missing or why that matters. When absence is a real problem for the caller, turn it into an error that does say so.
?? fail
A small price list where an unknown code is simply absent:
func PriceOf(code: int) -> int? {
return match code {
1 => 250,
2 => 120,
3 => 75,
else => none
};
}
Total needs a price, and a missing one is a failure worth reporting:
func Total(code: int, quantity: int) -> int ! UnknownProduct {
let price = PriceOf(code) ?? fail UnknownProduct { code: code };
return price * quantity;
}
This is the ?? from Coalesce, with fail as its fallback — just as Coalesce exit used return. When the price is present, price is that number. When it is absent, the function fails right there, and the error can carry whatever context is at hand — here, the code that was not found.
flowchart LR
look["PriceOf(code)"] --> q{"Present or none?"}
q -- "present" --> price["price is the number;<br/>Total carries on"]
q -- "none" --> fail["fail UnknownProduct { code }"]
fail --> caller["the caller learns<br/>which code was missing"]The error is built only when needed
Like every ?? fallback, the right side runs only when it is needed. For a product that exists, the UnknownProduct value is never even built.
Why not ?
On an optional, ? passes the absence on — but only as absence. It works in a function that returns an optional, and Total returns a fallible. ? never invents an error for a missing value; ?? fail is how you supply one yourself.
| You write | When the price is missing, Total… |
|---|---|
PriceOf(code) ?? 0 | carries on with 0, so the order costs nothing |
PriceOf(code) ?? return … | succeeds at once with the value given to return |
PriceOf(code)? | is rejected: Total returns a fallible, not an optional |
PriceOf(code) ?? fail … | fails, saying which code it was |
The program
The whole lesson is one package in the Examples repository. Its comments explain every step.
// An optional says "nothing here" and stops there. `PriceOf(7)` returns
// `none`, and `none` cannot say which code was missing or why that matters.
// When absence is a real problem for the caller, turn it into an error that
// does say so:
//
// let price = PriceOf(code) ?? fail UnknownProduct { code: code };
//
// This is the `??` from the optionals part with `fail` as its fallback. When
// the price is present, `price` is that number. When it is absent, the
// function fails right there, and the error can carry whatever context is at
// hand — here, the code that was not found.
//
// Like every `??` fallback, the right side runs only when it is needed, so
// the error is not even built for a product that exists.
import Io::PrintLine;
struct UnknownProduct {
code: int;
}
// A small price list. An unknown code is simply absent.
func PriceOf(code: int) -> int? {
return match code {
1 => 250,
2 => 120,
3 => 75,
else => none
};
}
func Total(code: int, quantity: int) -> int ! UnknownProduct {
let price = PriceOf(code) ?? fail UnknownProduct { code: code };
return price * quantity;
}
func Order(code: int, quantity: int) {
match Total(code, quantity) {
.Success(total) => PrintLine("{} x product {}: {} cents", quantity, code, total),
.Failure(e) => PrintLine("{} x product {}: no product has code {}", quantity, code, e.code)
}
}
func Main() -> int {
Order(1, 2);
Order(3, 4);
Order(7, 1);
return 0;
}
Run it
cd Examples/Errors/AbsenceToError
rux run
2 x product 1: 500 cents
4 x product 3: 300 cents
1 x product 7: no product has code 7
Common mistakes
let price = PriceOf(code)?; in Total is error: '?' propagates the absence of 'int?', but the enclosing function returns 'int ! UnknownProduct', with the note '?' never invents an error for it. Write ?? fail with the error you mean.In
Main, PriceOf(1) ?? fail UnknownProduct { code: 1 } is error: 'fail' needs an enclosing fallible function, but this function returns 'int'.== binds tighter than ??, so PriceOf(1) ?? 0 == 250 means PriceOf(1) ?? (0 == 250) and fails with error: coalescing fallback has type 'bool8', but the optional payload is 'int'. Write (PriceOf(1) ?? 0) == 250.Try it yourself
- Add product 4 at 990 cents and order three of them.
- Refuse an order of zero items as well. One error struct no longer fits — try a variant with an
UnknownCode(int)case and aNoItemscase. - Change
Order(7, 1)to usePriceOf(7) ?? 0directly inMaininstead ofTotal. What does the customer pay for an unknown product?
Learn more
- Coalesce and Coalesce exit —
??with a value and withreturn - Optional propagate —
?on an optional - Fail — the statement used as the fallback here
9.12 Fallible main
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.
9.14 Nested fallible
Keep two meanings apart by nesting: int? ! E for "the next value, the end, or a failure", and (int ! E1) ! E2 for an answer that can itself be a failure.