Reference

std/core/result

std/core/src/result.trb

Failure as a value: Result, its two cases, and the vocabulary that works on the value of a successful one.

There are no exceptions in the language. A function that can fail says so in its result type, ? passes a failure up to the caller, and a failure that nobody handles is therefore impossible to overlook. panic is the other half of the story and belongs to bugs, not to failures a program can answer.

Related

type Result

type Result<Value, Failure> with OrElse<Value>

The result of an operation that can fail: Ok with the value, or Fail with the reason.

Reach for it wherever failure is part of the answer and not a bug: reading a file, parsing text, talking to a server. The postfix ? unwraps an Ok and returns the Fail from the surrounding function, converting the error type through From on the way, so a chain of fallible steps reads like a chain of ordinary ones.

map, flatMap and forEach mean the same as everywhere else (see "One Vocabulary" in CONCEPT.md), work on the Ok side, and run immediately - a Result is a value, not a pipeline.

? is what a caller writes instead of unwrapping the value: quarter below answers with the failure of the step that failed, and says nothing about it.

Examples

fn half(value: Int): Result<Int, String> {
  if value % 2 == 0 { Ok(value / 2) } else { Fail "an odd number has no half" }
}

fn quarter(value: Int): Result<Int, String> {
  const once = half(value)?
  half once
}

print half(3)
print quarter(8)

Pitfalls

  • ? needs a surrounding function whose result is a Result. At the top level of a script there is nothing to return to, so a script answers a failure with Result.orElse or with a match.

Open

There is no counterpart of Result.flatMap for the failing side: recovering from a Fail with another operation that can fail itself is written with a match.

Related

  • Option - the answer when the absence needs no reason. Result.ok converts one into the other.
  • Result.mapError - what a ? between two error types does by itself.

case Ok

case Ok(value: Value)

The operation worked, and this is what it produced.

case Fail

case Fail(error: Failure)

The operation failed, and this is why.

fn isOk

fn isOk(): Bool

Whether the operation worked. For the value itself, match or ? instead.

fn isError

fn isError(): Bool

Whether the operation failed.

fn map

fn map<Output>(transform: Transform<Value, Output>): Result<Output, Failure>

Changes the value of an Ok and leaves a Fail alone: one more step on the way that worked.

fn mapError

fn mapError<OtherFailure>(transform: (error: Failure) => OtherFailure): Result<Value, OtherFailure>

Changes the reason of a Fail and leaves an Ok alone: for a caller that answers with an error type of its own.

? does this by itself wherever the two error types are connected by From. This is the way to write down what happened where they are not.

Examples

const parsed = Int.tryFrom "nine"
print parsed.mapError({ _ => "nine is not a number" })

fn flatMap

fn flatMap<Output>(transform: Transform<Value, Result<Output, Failure>>): Result<Output, Failure>

Continues with a step that can fail itself and keeps one level: the explicit form of what ? does.

fn forEach

fn forEach(action: Action<Value>)

Runs the action if the operation worked: the statement form of Result.map.

fn toList

fn toList(): List<Value>

Into the world of pipelines: a list with the value of an Ok, and an empty one for a Fail.

fn ok

fn ok(): Value?

The value as an Option, which drops the reason: for a caller that only wants to know whether there is one.

fn orElse

fn orElse(fallback: lazy Value): Value

The value, or the fallback if the operation failed. The way out of the type where the reason does not matter.

The fallback is lazy, so it is only evaluated when it is needed.

Examples

const parsed = Int.tryFrom "nine"
print parsed.orElse(0)

fn expect

fn expect(message: String): Value where Failure: Show

The value, with the message and the reason if the operation failed. For a place where a failure is a bug.

Panics

When the result is a Fail. The panic prints the message and the error, so the message says what was expected of the operation, not that it failed.

extend Result<Value, Failure> with Show

extend<Value: Show, Failure: Show> Result<Value, Failure> with Show

Ok(value) or Fail(failure).

fn show

fn show(): String

extend Result<Target, Failure> with From<Iterate<Result<Value, Failure>>>

extend< Value, Failure, Target: From<Iterate<Value>>, > Result<Target, Failure> with From<Iterate<Result<Value, Failure>>>

All or the first error: lines.map { text => Int.tryFrom text }.to<Result<List<Int>, NumberParseError>>() Stops pulling at the first Fail. This is what traverse/sequence is in languages with higher-kinded types.

Errors

The reason of the first Fail of the pipeline. Nothing behind it is pulled, so a step that fails early costs nothing more.

fn from

static fn from(value: Iterate<Result<Value, Failure>>): Result<Target, Failure>