Result
A function that can fail answers Result<Value, Failure>, whose cases are Ok and Fail. The postfix question mark unwraps an Ok or returns the Fail from the surrounding function.
Diese Seite gibt es noch nicht auf Deutsch, deshalb steht sie hier auf Englisch. Zur englischen Seite
There are no exceptions. A function that can fail says so in its result type, and the caller cannot ignore it. Result
is an ordinary type with two cases, and the postfix ? operator is the only thing about it that is syntax.
Example#
use File from "std/fs"
use IoError from "std/fs"
fn firstLine(path: String): Result<String, IoError> {
const text = File.readText(path)?
const lines = text.lines()
match lines.first() {
Some(line) => line
None => ""
}
}
print firstLine("project.trb")Syntax#
Result<Value, Failure> the type
value the success, where a Result is expected: it becomes Ok(value)
Ok(value) Ok value the success case, written out
Fail(error) Fail problem the failure case
<expression>? unwrap an Ok, or return the Fail from the surrounding function
<expression> ?? <fallback> the value, or the fallback (the fallback is lazy)Ok and Fail are written bare because the prelude imports them from Result.
Rules#
Result<Value, Failure>has exactly two cases:case Ok(value: Value)andcase Fail(error: Failure). It is declared instd/coreand is not built into the language.?unwraps anOkand returns theFailfrom the surrounding function. It is only legal in a function whose result is aResultor anOption, and at the top level of an entry file or a script.?converts the error type throughFrom. If the surrounding function answersResult<Value, AppError>and the expression answersResult<Value, IoError>, the conversionAppError.from(ioError)is applied. A case that wraps one value of a unique type gets thatFromgenerated, so nobody writes it:use IoError from "std/fs" type AppError { case Io(cause: IoError) case Startup(message: String) } fn start(text: String): Result<Int, AppError> { if text.isEmpty() { return Fail AppError.Startup("nothing to do") } text.byteLength() } print start("hello")The success is the value itself. A value of exactly
Valuewhere aResult<Value, Failure>is expected - the end of a body, areturn, an argument, a binding with an annotation - becomesOk(value)on its own, the fifth coercion of Conversions.Okis written only where that does not happen: where theValueis not decided yet, and in the payload of a nestedOk. A value that is anOptionor aResultitself wraps into theOkonly where it is exactly theValue(Result<Int?, Failure>from anInt?). The failure is always written:return Fail problem.fn percent(value: Int): Result<Int, String> { if value < 0 || value > 100 { return Fail "{value} is not a percentage" } value } print percent(42) print percent(142)A call in command position writes its argument without parentheses, which is why
return Fail problemis the canonical form, andOk Some(x)has parentheses on the inner call only.??isOrElse.orElse, andOptionandResultboth come with that trait. The right side islazy, so it is evaluated only when it is needed, and it is checked against theValuerather than against the wholeResult.fn parseOr(text: String, fallback: Int): Int { Int.tryFrom(text) ?? fallback } print parseOr("12", 0) print parseOr("nonsense", 0)?.is not defined onResult. It isOption.map, andOption.flatMapwhen the member answers anOption, so it never produces a nestedOption. To go from aResultto anOption, callok().Resultshares the vocabulary ofOption,TaskandIterate:map,flatMap,forEach,orElse,toList(). They mean the same thing everywhere, by convention rather than through higher-kinded types.mapandflatMapwork on theOkside;mapErrorworks on theFailside.isOk(),isError(),ok()andexpect(message)are the rest of the surface.expectpanics with the message and the error, and is for cases where a failure is a bug.Precise error types are the norm for a library. A caller can only
matchon what a signature names.Result<Value, Error>- whereErroris a trait from the prelude - is for the layers above, where the only thing left to do with a failure is to report it. Every error type that carrieswith Errorfits into it through the ordinary coercion of a value to a trait-typed value.A collection of results collects into a result of a collection.
to<Result<List<Int>, ParseError>>()stops at the firstFail. That is whattraverseorsequenceis in a language with higher-kinded types, and it is an ordinaryFrom<Iterate<...>>implementation rather than a language feature.A top-level
?is not a panic. In an entry file or a script it printserror: <the error through Show>and exits with 1 (thecaused by:lines of the chain are specified and not built yet, see Errors at the top level). Apanicprintspanic: <message>and the site, exits with 101, and runs nothing on the way out - noClose, nousingcleanup.
What this is not#
Fail is not Err. The case is called Fail because Error is the name of the trait every error type implements,
and because return Fail problem reads as what it does. The field keeps the name error, and so do isError and
mapError, which are about the error the case carries.
fn checked(value: Int): Result<Int, String> {
if value < 0 {
return Fail "negative"
}
value
}
print checked(1)fn checked(value: Int): Result<Int, String> {
if value < 0 {
return Err("negative")
}
value
}
// error: Cannot find `Err` here? is not try and it is not a throw. It is an early return. Nothing is unwound, nothing is caught, and there is no
handler anywhere. A function that does not want to return early matches on the Result instead.
A panic is not an exception. It cannot be caught, it aborts the process, and nothing runs while the program falls over, because a panic means the program has a bug and running more code in a broken program is how bugs get worse. The one exception is a sandboxed script, which the host can stop and report.
Result is not for absence. A value that may be missing is an Option<Value>, written Value?. A Result carries a
reason.
Related#
- Cases and match - how
OkandFailare taken apart. - std/core - where
Result,OptionandErrorare declared. - Read a file - the shortest complete use of
?. - Define an error type - one type with cases for a whole application.
- Coming from Rust -
ErragainstFail, and what?converts.