Option and Result
Every program can use two generic enums without declaring them:
enum Option[T] do
SOME(T value)
NONE
end
enum Result[T, E] do
OK(T value)
ERROR(E error)
endOption is a value that may be missing, and Result is the value of an
operation that can fail, or the error that says why it failed. Result[T]
is Result[T, Error]; another error type, such as an enum of the ways a
parser can fail, is written as the second type argument. A function returns
them like any other value, and match makes the caller handle both cases,
so a missing value or a failure cannot be overlooked:
func find_word(str[] words, str word) Option[int] do
for i = 0 to len(words) - 1 do
if words[i] == word then
return Option.SOME(i)
end
end
return Option.NONE
end
match parse_int(text) do
when OK(value) then
print(value * 2)
when ERROR(message) then
eprint(message)
endThe names Option, Result, and Error cannot be declared again.
Errors
Error, a struct of the prelude, describes what went wrong:
struct Error do
str message
str kind = "error"
Error cause = null
str file = ""
int line = 0
endkind tells errors apart for match, and cause is the error this one
adds to. file and line say where it was made: Error("not found", kind: "not_found") fills them in with its own place. A str given where an
Error is expected becomes one made there, so Result.ERROR("no '+' here")
needs no constructor, and an Error used where a str is expected, or
joined to one with +, is its text: its message followed by those of its
causes, as e.to_text() gives and print shows. e.trace() gives each
error of the chain on a line of its own with its place.
The built-ins that fail make errors with these kinds: parse_int and
parse_float give "invalid" or "out_of_range", and try_read_text
gives "not_found", "permission", "invalid" for text that is not UTF-8,
or "io".
match try_read_text(path) do
when OK(text) then
print(len(text))
when ERROR(e) then
match e.kind do
when "not_found" then
print("no such file")
else
eprint(e)
end
endpanic(message) is for states that should be impossible: it stops the
program with a runtime error, panic: message, and the trace of where it
was. A function may end with panic(...) or exit(...) where it would
otherwise need a return.
catch_panic(body) runs the function body and gives "" when it ends,
or the message of the runtime error that stopped it, a panic among them,
as panic: message or array index out of bounds. The functions it was
in are left, and their deferred code runs. It is for programs that run
code they cannot trust to be right, as a compiler checking each statement
of a program, or a server keeping one request's failure from the others;
errors that are expected are Results.
str problem = catch_panic(func() do
handle(request)
end)
if problem != "" then
log.error("request failed", {"problem": problem})
endPassing errors on with ?
In a function that returns a Result, value? is what an OK holds, and
an ERROR makes the function return that error at once. In a function that
returns an Option, it does the same with SOME and NONE:
// Reads "a+b" and adds the numbers, or says what was wrong.
func add_text(str text) Result[int] do
int plus = find(text, "+")
if plus < 0 then
return Result.ERROR("no '+' in '" + text + "'")
end
int left = parse_int(trim(slice(text, 0, plus)))?
int right = parse_int(trim(slice(text, plus + 1, len(text))))?
return Result.OK(left + right)
endA call with ? can stand alone as a statement when only its error
matters, as in close_file(f)?.
value ? "message" passes on a new error with that message, whose cause is
the error of value and whose kind is the same, so the caller learns what
was being done as well as what failed:
int port = parse_int(text) ? f"reading the port from '{text}'"
// reading the port from 'x80': 'x80' is not an integerWith a message, ? also works on an Option in a function that returns a
Result: a NONE becomes an error with that message. When the function's
error type differs from the value's, ? converts the error with the
function's error type's from, a function of the type such as
func from(Error e) AppError, and it is an error when there is none.
In the program body, outside any function, an ERROR or NONE met by ?
ends the program: the error's text, after the message if one is written,
goes to standard error, and the program exits with status 1. Small programs
and tools can use ? without a function around it:
str config = try_read_text("app.conf") ? "reading the settings"
int workers = parse_int(trim(config))?? may be used in a declaration, an assignment's value, a return, a call
statement, the condition of an if (not of an else if), the value of a
match, and the collection of a for ... in. Since its check runs before
the rest of the statement, a ? cannot come after and or or, inside a
function written with =>, or after a value in the same expression that a
call could change, such as items[0] + parse_int(text)?; store that value
in a variable first.