Skip to content

Result<T, E>

Gently return errors instead of rudely throwing them using Result<T, E>

Creating a Result<T, E> instance

To create a Result, use the Ok() and Err() functions.

import { Ok, Err } from "@oofdere/crabrave";
const ok = Ok<string, number>("success");
const err = Err<string, number>(1);

You’ll generally want to create Result<T, E> instances inside functions and return them, like so:

import { Ok, Err, Result, Enum, pack } from "@oofdere/crabrave";
type PercentError = {
DivByZero: number,
TotalLessThanN: number,
NegativeResult: number
}
function percent(n: number, total: number): Enum<Result<string, Enum<PercentError>>> {
const p = n / total * 100;
if (total < n) return Err(pack("TotalLessThanN", p));
if (total === 0) return Err(pack("DivByZero", p));
if (p < 0) return Err(pack("NegativeResult", p));
return Ok(`${p}%`);
}

TypeScript successfully infers all of our return types when using packed enums, so no need to pass generics!

Using a Result<T, E> instance

Now let’s assume that the percent() function we just created is part of a library and we want to use it in our code:

import { percent } from "percentString";
const half = percent(1, 2); //=> Enum<Result<string, Enum<PercentErrors>>>
console.log(half); // [ "Ok", "50%" ]
const double = percent(2, 1); //=> Enum<Result<string, Enum<PercentErrors>>>
console.log(double); // [ "Err", [ "TotalLessThanN", 200 ] ]

Notice that despite half containing an Ok result and double containing an Err result, they still have the same type.

match(): Verbose but exhaustive

To get our value out of the Result, we can use match():

const half = percent(1, 2);
match(half, {
Ok: (x) => console.log("ok:", x),
Err: (x) => console.log("error:", x)
}); // ok: 50%
const double = percent(2, 1);
match(double, {
Ok: (x) => console.log("ok:", x),
Err: (x) => console.log("error:", x)
}); // error: [ "TotalLessThanN", 200 ]

When our Err is an enum, like it is here, we can nest match statements to handle the full chain:

const double = percent(2, 1);
match(double, {
Ok: (x) => console.log("ok:", x),
Err: (err) => match(err, {
DivByZero: (x) => console.log("Divided by zero!", `${x}%`),
TotalLessThanN: (x) => console.log("Total is less than points earned!", `${x}%`),
NegativeResult: (x) => console.log("Negative percentage!", `${x}%`),
})
}); // Total is less than points earned! 200%

unwrap(): Terse, but explosive

A simpler, but unsafe way of extracting the value is to use the unwrap() function:

const half = percent(1, 2);
console.log(half.unwrap()); // 50%

Really simple, right? Why deal with all the extra code and overhead of that matching nonsense when you can just unwrap your value like a present?

Well, the problem is that this present might throw a grenade at you when you open it:

const double = percent(2, 1);
console.log(double.unwrap());
Terminal window
TypeError: Err(): TotalLessThanN,200
at /home/teo/crabrave/src/unwrap.ts:20:7
at /home/teo/crabrave/examples/percent.ts:24:12

You should only use unwrap() if you’re absolutely, positively sure that the function will not return an Err, and it’s best saved for prototyping, after which you can replace them with match() and or() statements to make them safer.

Avoid unwrap() in production.

or(): A solid middle ground

or() is the exact same as unwrap(), except that instead of crashing your program when it encounters an Err it’ll return the fallback value you pass into it:

const half = percent(1, 2);
console.log(half.or("0%")); // 50%
const double = percent(2, 1);
console.log(double.or("0%")); // 0%