Errors you have to handle
A function that can fail says so after !, and its caller gets a
Result. Errors are ordinary values, usually an enum.
errors/main.xo
enum ConfigErr {
NotANumber(s: Str)
OutOfRange(n: Int)
}
fn (e: ConfigErr) display() -> Str {
match e {
NotANumber(s) => "not a number: ${s}"
OutOfRange(n) => "out of range: ${n}"
}
}
fn parse_port(s: Str) -> Int ! ConfigErr {
let n = Int.parse(s).or_err(ConfigErr.NotANumber(s))?
if n < 1 || n > 65535 {
return Err(ConfigErr.OutOfRange(n))
}
n
}
enum StartErr {
Config(from ConfigErr)
}
fn start(port: Str) -> Str ! StartErr {
let p = parse_port(port)?
"listening on ${p}"
}
fn main(os: Os) {
for input in ["8080", "abc", "99999"] {
match start(input) {
Ok(msg) => os.stdio.println(msg)
Err(Config(e)) => os.stdio.println("error: ${e.display()}")
}
}
let fallback = parse_port("x").ignore_err("the default port is fine") ?? 8080
os.stdio.println("fallback ${fallback}")
}Output:
listening on 8080
error: not a number: abc
error: out of range: 99999
fallback 8080
-> Int ! ConfigErrreturns either anIntor aConfigErr.expr?returns the error to the caller. Here?instartturns aConfigErrinto aStartErrthrough thefromvariant.Int.parse(s)returnsInt?;.or_err(...)turns a missing value into an error.matchon the result handles both outcomes.
Ignoring an error is a compile error, not a lint:
fn parse_port(s: Str) -> Int ! Error {
Int.parse(s).or_err("not a number")?
}
fn main(os: Os) {
parse_port("8080")
}xo check says:
main.xo:6:3: error XO0201: result of `parse_port` is not handled
note: `parse_port` can fail with `Error`; propagate with `?`, match on it, or discard it with `.ignore_err("reason")` (core 2.6)
fix: discard with a reason
1 module(s), 1 error(s), 0 warning(s)
When a failure really does not matter, say why:
parse_port(s).ignore_err("the default port is fine"). The reason is in the
code for every reviewer to see.