Brine has no exceptions. A value that may be missing is an Option[T], an operation
that may fail returns a Result[T, E], and x? passes a None or Err straight back
to the caller. Panics are for bugs, not for failures you expect.
Option and Result
Section titled “Option and Result”Both are generic enums that every program has without an import:
enum Option[T]: Some(value: T) None
enum Result[T, E]: Ok(value: T) Err(error: E)Their variants are written on their own: Some(3), None, Ok(x), Err("bad input").
Handle them with match, which makes you deal with both
cases:
fn find(names: [str], target: str) -> Option[int]: for i, name in enumerate(names): if name == target: return Some(i) return None
fn parse_bit(s: str) -> Result[int, str]: if s == "0": return Ok(0) if s == "1": return Ok(1) return Err(f"not a bit: {s}")
fn main(): let names = ["ann", "bob"] match find(names, "bob"): case Some(i): print(f"bob is at {i}") case None: print("no bob") match parse_bit("x"): case Ok(bit): print("bit", bit) case Err(e): print("failed:", e)bob is at 1failed: not a bit: xThe error type E can be anything: a str message, an int code, or your own enum
of failure kinds.
None on its own doesn’t say which Option it is. Where the context doesn’t either,
annotate: let n: Option[int] = None (otherwise E0332).
Methods
Section titled “Methods”fn parse_bit(s: str) -> Result[int, str]: if s == "0": return Ok(0) if s == "1": return Ok(1) return Err(f"not a bit: {s}")
fn main(): let good = parse_bit("1") let bad = parse_bit("7") print(good.is_ok(), good.is_err(), bad.is_err()) print(good.unwrap(), bad.unwrap_or(-1)) print(good.ok(), bad.ok())
let some: Option[str] = Some("x") let none: Option[str] = None print(some.is_some(), none.is_none()) print(some.unwrap(), none.unwrap_or("default"))true false true1 -1Some(1) Nonetrue truex default| Method | On | Returns |
|---|---|---|
is_some(), is_none() |
Option[T] |
bool |
is_ok(), is_err() |
Result[T, E] |
bool |
unwrap() |
both | the value inside Some/Ok; panics on None/Err |
unwrap_or(default) |
both | the value inside, or default |
ok() |
Result[T, E] |
Option[T]: Some(value) or None (drops the error) |
That’s the complete list. To get at an error’s value, or to turn an Option into a
Result, use match.
unwrap() is for when a None or Err would be a bug. If it happens, the program
stops:
fn main(): let port: Option[int] = None print(port.unwrap())panic: unwrap() called on None --> unwrap.br:3:11The ? operator
Section titled “The ? operator”x? unwraps an Option or Result: it gives the value inside Some/Ok, or returns
the None/Err from the current function at once.
fn parse_bit(s: str) -> Result[int, str]: if s == "0": return Ok(0) if s == "1": return Ok(1) return Err(f"not a bit: {s}")
fn parse_bits(text: str) -> Result[int, str]: var n = 0 for c in text: n = n * 2 + parse_bit(c)? # an Err is returned to the caller here return Ok(n)
fn first_even(xs: [int]) -> Option[int]: for x in xs: if x % 2 == 0: return Some(x) return None
fn half_first_even(xs: [int]) -> Option[int]: let e = first_even(xs)? # None is returned here return Some(e / 2)
fn main(): print(parse_bits("101"), parse_bits("1x1")) print(half_first_even([3, 8]), half_first_even([1, 3]))Ok(5) Err("not a bit: x")Some(4) NoneThe rules:
?on anOptionneeds the function to return anOption[...].?on aResult[T, E]needs the function to return aResult[..., E]with the same error typeE. Errors are never converted for you.?can appear anywhere in an expression, as inn * 2 + parse_bit(c)?.
Breaking a rule is E0341:
fn first_even(xs: [int]) -> Option[int]: for x in xs: if x % 2 == 0: return Some(x) return None
fn main(): let e = first_even([1, 2])? print(e)error[E0341]: `?` needs the function to return Option[...] --> main.br:8:13 |8 | let e = first_even([1, 2])? | ^^^^^^^^^^^^^^^^^^^ this returns from a function that returns `()` | = help: change the return type to `Option[...]`, or handle the value with `match` = run `brine explain E0341` for more about this errorerror: could not compile due to 1 errorfn parse_bit(s: str) -> Result[int, str]: if s == "1": return Ok(1) return Err(f"not a bit: {s}")
fn checked(s: str) -> Result[int, int]: let b = parse_bit(s)? return Ok(b)
fn main(): print(checked("1"))error[E0341]: `?` would return a `str` error from a function whose errors are `int` --> main.br:7:13 |7 | let b = parse_bit(s)? | ^^^^^^^^^^^^^ the error here is `str` | = help: convert it with `match`, or make the function's error type match = run `brine explain E0341` for more about this errorerror: could not compile due to 1 errorConverting between error types
Section titled “Converting between error types”Convert with match where the types differ. To go from a Result to an Option, use
.ok() and then ?:
fn parse_bit(s: str) -> Result[int, str]: if s == "1": return Ok(1) return Err(f"not a bit: {s}")
fn code_of(s: str) -> Result[int, int]: match parse_bit(s): case Ok(b): return Ok(b) case Err(_): return Err(2)
fn bit_or_none(s: str) -> Option[int]: let b = parse_bit(s).ok()? # Result to Option, then `?` return Some(b)
fn main(): print(code_of("1"), code_of("x"), bit_or_none("1"), bit_or_none("x"))Ok(1) Err(2) Some(1) Nonemain returning Result
Section titled “main returning Result”main may return Result[(), E], which lets it use ?. An Err that reaches the end
of main prints error: and the error to stderr, and the program exits with code 1.
File I/O errors are values
Section titled “File I/O errors are values”The file and process built-ins never crash on failure; they return a Result (or an
Option or bool) and the failure is a value you handle:
| Built-in | Returns |
|---|---|
read_file(path) |
Result[str, str]: the text (must be UTF-8), or the reason it couldn’t be read |
write_file(path, text) |
Result[(), str] |
file_exists(path) |
bool |
getenv(name) |
Option[str] |
run(cmd) |
Result[int, str]: Ok(exit code), or Err if it can’t start |
Together with ? and a main that returns Result, a program reads like the happy
path and still handles every failure:
fn count_lines(path: str) -> Result[int, str]: let text = read_file(path)? return Ok(len(text.lines()))
fn main() -> Result[(), str]: write_file("notes.txt", "one\ntwo\nthree\n")? print(count_lines("notes.txt")?) match count_lines("missing.txt"): case Ok(n): print(n) case Err(e): print("skipped:", e) print(count_lines("settings.txt")?) print("not reached") return Ok(())3skipped: cannot read missing.txt: No such file or directoryerror: cannot read settings.txt: No such file or directoryThe os module’s functions work the same way; see the os module
and built-ins.
Panics vs expected failures
Section titled “Panics vs expected failures”Use Option and Result for failures a correct program must expect: missing files,
bad input, a key that isn’t there. Use a panic for a bug: a state that should be
impossible.
A panic stops the program with a message, the source location, and exit code 101. You can’t catch one. Panics come from:
- the language’s own checks: integer overflow, division by zero, an out-of-bounds index, an out-of-range conversion (see values and types);
unwrap()onNoneorErr, and parsing bad text withint(s)orfloat(s);assert cond, which checks at run time and also tells the prover the condition holds afterwards (see contracts);panic(msg), which you call yourself.
fn average(xs: [float]) -> float: if len(xs) == 0: panic("average of an empty array") return sum(x for x in xs) / float(len(xs))
fn main(): print(average([1.0, 2.0])) let none: [float] = [] print(average(none))1.5panic: average of an empty array --> average.br:3:9Better still, turn the bug into something the compiler checks:
requires len(xs) > 0 makes every caller prove the array isn’t empty, at compile time
(see contracts).
Summary
Section titled “Summary”Option[T]isSome(value)orNone;Result[T, E]isOk(value)orErr(error). Both are always available.- Methods:
is_some,is_none,is_ok,is_err,unwrap(panics),unwrap_or,ok. Everything else ismatch. x?returnsNone/Errearly; the function must returnOption[...], orResult[..., E]with the sameE(E0341).mainmay returnResult[(), E]: anErrprintserror: ...and exits with 1.- File and process built-ins return failures as values.
- Panics (exit 101) are for bugs and can’t be caught.