Result
Result<T, E>
is the type used for
returning and propagating errors. It is a type with the variants,
Ok<T>
, representing success and containing
a value, and Err<E>
, representing error
and containing an error value.
Unlike in Rust, in JavaScript errors may occur at any time - builtin module may
throw, 3rd party package may throw, runtime errors are all over the place.
Result
type from @ts-rust/std
package
aims to leverage this behavior by offering an API that is "throwless". As long as
you use the API provided by Result
,
all possible errors will be handled gracefully and returned as
Err<E>
variant. This is achieved by
using CheckedError<E>
type.
Let's take a look at the example so you can see how it works in practice.
Say we have a function that returns a result with either a number or an error:
function getNumberResult(): Result<number, string> {
if (new Date().getDay() === 0) {
// if today is Sunday, we throw an error
// note: this is just an example! never do that in production code
throw new Error("no numbers on Sundays");
}
return Math.random() > 0.5 ? ok(2) : err("random error");
}
functions runResult
, ok
, err
as well as type Result
are imported from @ts-rust/std
package.
In the example above, we throw an exception if today is Sunday. This is just an example
to illustrate how Result
in this library works. In production code,
you should never throw exceptions from functions whose return type is Result<T, E>
!
We can use now runResult
function to execute the function and handle the result safely.
runResult
will catch any errors thrown by the function and return an Err<E>
variant if
an error occurs.
For functions that return raw values instead of Result
, you can use run
function instead. run
will return a Result<T, E>
where T
is the type of
the value returned by the function
const result: Result<number, string> = runResult(getNumberResult);
From this point on, we can safely work with the result
variable.
If the result is Ok<number>
, we can safely access the value.
if (result.isOk()) {
console.log(`Got number: ${result.value}`); // (property) value: number
return;
}
If the result is not Ok<number>
, we can handle the error. Thanks to the type inference,
typescript knows that result
is of type Err<string>
(due to the way how isOk
and isErr
are implemented).
const { error } = result; // const error: CheckedError<string>
if (error.isExpected()) {
// if error is expected, (e.g. `getNumberResult` returned `err(...)`) we can access the error value
console.log("Expected error of type string:", error.expected); // (property) expected: string
}
if (error.isUnexpected()) {
// the same way, if the error is unexpected (e.g. `getNumberResult` threw an exception),
// we can access the underlying error and its reason
console.log("Unexpected error:", error.unexpected); // (property) unexpected: ResultError
console.log("Error reason:", error.unexpected.reason); // (property) AnyError<ResultErrorKind>.reason: Error
}
Variants
Ok<T>
- Ok value of typeT
.Err<E>
- Error value of typeCheckedError<E>
.
Constructors
ok(value: T)
- createsOk<T>
variant.err(err: E)
- createsErr<E>
variant.
Methods
and
and<U>(x: Result<U, E>): Result<U, E>
Returns x
if this result is Ok
, otherwise returns the Err
value of self.
const x = ok<number, string>(1);
const y = ok<number, string>(2);
const z = err<number, string>("failure");
expect(x.and(y)).toStrictEqual(ok(2));
expect(x.and(z)).toStrictEqual(err("failure"));
expect(z.and(x)).toStrictEqual(err("failure"));
andThen
andThen<U>(f: (x: T) => Result<U, E>): Result<U, E>
Applies f
to the value if this result is Ok
and returns its result,
otherwise returns the Err
value of self.
const x = ok<number, string>(2);
const y = err<number, string>("failure");
expect(x.andThen((n) => ok(n * 2))).toStrictEqual(ok(4));
expect(y.andThen((n) => ok(n * 2))).toStrictEqual(err("failure"));
check
check(this: SettledResult<T, E>): this extends Ok<T, E> ? [true, T] : [false, CheckedError<E>]
Inspects the Result
’s state, returning a tuple indicating success and either a value or an error.
- Only available on
Result
s that areSettled
. - Returns
[true, T]
if this is anOk
, or[false, CheckedError<E>]
if this is anErr
. - Never throws, providing a safe way to access the result’s state without unwrapping.
const x = ok<number, string>(42);
const y = err<number, string>("failure");
expect(x.check()).toEqual([true, 42]);
expect(y.check()).toEqual([false, expect.objectContaining({ expected: "failure" })]);
clone
clone<U, F>(this: Result<Cloneable<U>, Cloneable<F>>): Result<U, F>
Returns a clone of the Result
.
Only available on Result
s with Cloneable
value and error.
class CloneableClass implements Clone<CloneableClass> {
constructor(public a: number) {}
clone(this: Clone<CloneableClass>): CloneableClass;
clone(this: CloneableClass): CloneableClass;
clone(): CloneableClass {
return new CloneableClass(this.a);
}
}
const cloneable = new CloneableClass(1);
const x = ok<CloneableClass, string>(cloneable);
const y = err<CloneableClass, string>("oops");
expect(x.clone()).not.toBe(x); // Different reference
expect(x.clone()).toStrictEqual(cloneable);
expect(y.clone().unwrapErr().expected).toBe("oops");
combine
combine<U extends Result<unknown, E>[]>(...results: U): Result<[T, ...OkValues<U>], E>
Combines this Result
with other Result
instances into a single
Result
containing a tuple of values.
The combine
method takes an arbitrary number of Result
instances,
all sharing the same Err
type. If all Result
instances
(including this one) are Ok
, it returns a Result
with a tuple of
their Ok
values in the order provided. If any Result
is Err
, it returns
that Err
. The resulting tuple includes the value of this Result
as the first
element, followed by the values from the provided Result
instances.
const a = ok<Promise<number>, string>(Promise.resolve(1));
const b = ok<string, string>("hi");
const c = err<Date, string>("no");
const d = a.combine(b, c); // Result<[Promise<number>, string, Date], string>
copy
Returns a shallow copy of the Result
.
const value = { a: 1 };
const x = ok<{ a: number }, string>(value);
expect(x.copy()).toStrictEqual(ok({ a: 1 }));
expect(x.copy()).not.toBe(x); // Different result reference
expect(x.copy().unwrap()).toBe(value); // Same value reference
err
err(this: SettledResult<T, E>): Option<E>
Converts this Result
to an Option
containing the error, if present.
Returns Some
with the error value if this is an Err
, or None
if this is an Ok
.
- Only available on
Result
s that areSettled
. - Extracts the error from
CheckedError
if it’s anExpectedError
; returnsNone
forUnexpectedError
.
const x = ok<number, string>(1);
const y = err<number, string>("failure");
expect(x.err()).toStrictEqual(none());
expect(y.err()).toStrictEqual(some("failure"));
expect
expect(this: SettledResult<T, E>, msg?: string): T
Retrieves the error if this result is an Err
, or throws a ResultError
with an optional message if it’s an Ok
.
Only available on Result
s that are Settled
.
Throws ResultError
if this result is an Ok
.
const x = ok<number, string>(42);
const y = err<number, string>("failure");
expect(() => x.expectErr("Failed!")).toThrow(ResultError);
expect(isCheckedError(y.expectErr("Failed!"))).toBe(true);
expect(y.expectErr("Failed!").expected).toBe("failure");
expectErr
expectErr(this: SettledResult<T, E>, msg?: string): CheckedError<E>
Retrieves the error if this result is an Err
, or throws a ResultError
with an optional message if it’s an Ok
.
Only available on Result
s that are Settled
.
Throws ResultError
if this result is an Ok
const x = ok<number, string>(42);
const y = err<number, string>("failure");
expect(() => x.expectErr("Failed!")).toThrow(ResultError);
expect(isCheckedError(y.expectErr("Failed!"))).toBe(true);
expect(y.expectErr("Failed!").expected).toBe("failure");
flatten
flatten<U, F>(this: Result<Result<U, F>, F>): Result<U, F>
Flattens a nested result (Result<Result<T, E>, E>
) into a single result (Result<T, E>
).
Only available on Result
s that hold another Result
with the error of same type E
.
const x: Result<Result<Result<number, string>, string>, string> = ok(ok(ok(6)));
const y: Result<Result<number, string>, string> = x.flatten();
const z: Result<Result<number, string>, string> = err("oops");
expect(x.flatten()).toStrictEqual(ok(ok(6)));
expect(y.flatten()).toStrictEqual(ok(6));
expect(z.flatten()).toStrictEqual(err("oops"));
inspect
inspect(f: (x: T) => unknown): Result<T, E>
Calls f
with the value if this result is Ok
, then returns a copy
of this result.
- Returns a new
Result
instance, not the original reference. - If
f
throws or returns aPromise
that rejects, the error is ignored.
const x = ok<number, string>(2);
const y = err<number, string>("failure");
let sideEffect = 0;
expect(x.inspect((n) => (sideEffect = n))).toStrictEqual(ok(2));
expect(x.inspect((_) => { throw new Error(); })).toStrictEqual(ok(2));
expect(sideEffect).toBe(2);
expect(y.inspect((n) => (sideEffect = n))).toStrictEqual(err("failure"));
expect(sideEffect).toBe(2); // Unchanged
inspectErr
inspectErr(f: (x: CheckedError<E>) => unknown): Result<T, E>
Calls f
with the error if this result is an Err
, then returns a copy
of this result.
- Returns a new
Result
instance, not the original reference. - If
f
throws or returns aPromise
that rejects, the error is ignored.
const x = ok<number, string>(2);
const y = err<number, string>("failure");
let sideEffect = 0;
expect(x.inspect((n) => (sideEffect = n))).toStrictEqual(ok(2));
expect(x.inspect((_) => { throw new Error(); })).toStrictEqual(ok(2));
expect(sideEffect).toBe(0); // unchanged
expect(y.inspect((n) => (sideEffect = n))).toStrictEqual(err("failure"));
expect(y.inspect((_) => { throw new Error(); })).toStrictEqual(err("failure"));
expect(sideEffect).toBe(2);
isErr
Checks if this result is an Err
, narrowing its type to Err
if true.
const x = ok<number, string>(2);
const y = err<number, string>("failure");
expect(x.isErr()).toBe(false);
expect(y.isErr()).toBe(true);
isErrAnd
isErrAnd(f: (x: CheckedError<E>) => boolean): this is Err<T, E> & boolean
Returns true
if the result is Err
and f
returns true
for the contained error.
If f
throws, false
is returned.
const x = ok<number, string>(2);
const y = err<number, string>("failure");
expect(x.isErrAnd((e) => e.expected === "failure")).toBe(false);
expect(y.isErrAnd((e) => e.expected === "failure")).toBe(true);
expect(y.isErrAnd((e) => Boolean(e.unexpected))).toBe(false);
isOk
Checks if this result is an Ok
, narrowing its type to Ok
if true.
const x = ok<number, string>(2);
const y = err<number, string>("failure");
expect(x.isOk()).toBe(true);
expect(y.isOk()).toBe(false);
isOkAnd
isOkAnd(f: (x: T) => boolean): this is Ok<T, E> & boolean
Returns true
if the result is Ok
and f
returns true
for the contained value.
If f
throws, false
is returned.
const x = ok<number, string>(2);
const y = err<number, string>("failure");
expect(x.isOkAnd((n) => n > 0)).toBe(true);
expect(x.isOkAnd((n) => n < 0)).toBe(false);
expect(y.isOkAnd((_) => true)).toBe(false);
iter
iter(): IterableIterator<T, T, void>
Returns an iterator over this result’s value, yielding it if Ok
or nothing if Err
.
- Yields exactly one item for
Ok
, or zero items forErr
. - Compatible with
for...of
loops and spread operators. - Ignores the error value in
Err
cases, focusing only on the success case.
const x = ok<number, string>(42);
const y = err<number, string>("failure");
const iterX = x.iter();
expect(iterX.next()).toEqual({ value: 42, done: false });
expect(iterX.next()).toEqual({ done: true });
const iterY = y.iter();
expect(iterY.next()).toEqual({ done: true });
expect([...x.iter()]).toEqual([42]);
expect([...y.iter()]).toEqual([]);
map
map<U>(f: (x: T) => Awaited<U>): Result<U, E>
Transforms this result by applying f
to the value if it’s an Ok
,
or preserves the Err
unchanged.
If f
throws, returns an Err
with an
UnexpectedError
containing the original error.
const x = ok<number, string>(2);
const y = err<number, string>("failure");
expect(x.map((n) => n * 2)).toStrictEqual(ok(4));
expect(x.map(() => { throw new Error("boom"); }).unwrapErr().unexpected).toBeDefined();
expect(y.map((n) => n * 2)).toStrictEqual(err("failure"));
mapAll
mapAll<U, F>(f: (x: Result<T, E>) => Result<U, F>): Result<U, F>
mapAll<U, F>(f: (x: Result<T, E>) => Promise<Result<U, F>>): PendingResult<Awaited<U>, Awaited<F>>
Maps this result by applying a callback f
to its full state, executing the callback
for both Ok
and Err
, returning a new Result
(or a PendingResult
,
if provided callback returns a Promise
).
Unlike andThen
, which only invokes the callback for Ok
,
this method always calls f
, passing the entire Result
as its argument.
If f
throws or returns a Promise
that rejects, an Err
(or a PendingResult
that resolves to an Err
) with an UnexpectedError
is returned.
const okRes = ok<number, string>(42);
const errRes = err<number, string>("failure");
expect(okRes.mapAll((res) => ok(res.unwrapOr(0) + 1))).toStrictEqual(ok(43));
expect(errRes.mapAll((res) => ok(res.unwrapOr(0) + 1))).toStrictEqual(ok(1));
expect(okRes.mapAll((res) => (res.isOk() ? ok("success") : err("fail")))).toStrictEqual(ok("success"));
expect(errRes.mapAll(() => { throw new Error("boom"); }).unwrapErr().unexpected).toBeDefined();
const mappedOk = okRes.mapAll((res) => Promise.resolve(ok(res.unwrapOr(0) + 1)));
expect(await mappedOk).toStrictEqual(ok(43));
const mappedErr = errRes.mapAll((res) => Promise.resolve(ok(res.unwrapOr(0) + 1)));
expect(await mappedErr).toStrictEqual(ok(1));
const mappedCheck = okRes.mapAll((res) => Promise.resolve(res.isOk() ? ok("success") : err("fail")));
expect(await mappedCheck).toStrictEqual(ok("success"));
const mappedThrow = errRes.mapAll(() => Promise.reject(new Error("boom")));
expect((await mappedThrow).unwrapErr().unexpected).toBeDefined();
mapErr
mapErr<F>(f: (e: E) => Awaited<F>): Result<T, F>
Transforms this result by applying f
to the error if it’s an Err
with
an expected error, or preserves the result unchanged.
- If
f
throws, returns anErr
with anUnexpectedError
containing the original error. - If this is an
Err
with anUnexpectedError
,f
is not called, and the original error is preserved.
const x = ok<number, string>(2);
const y = err<number, string>("failure");
expect(x.mapErr((e) => e.length)).toStrictEqual(ok(2));
expect(y.mapErr((e) => e.length)).toStrictEqual(err(7));
expect(y.mapErr(() => { throw new Error("boom"); }).unwrapErr().unexpected).toBeDefined();
mapOr
mapOr<U>(this: SettledResult<T, E>, def: Awaited<U>, f: (x: T) => Awaited<U>): U
Returns f
applied to the value if Ok
, otherwise returns provided default.
- Only available on
Result
s that areSettled
. f
has to return a synchronous (Awaited
) value.- If
f
throws, returnsdef
.
const x = ok<number, string>(2);
const y = err<number, string>("failure");
expect(x.mapOr(0, (n) => n * 2)).toBe(4);
expect(x.mapOr(0, () => { throw new Error("boom"); })).toBe(0);
expect(y.mapOr(0, (n) => n * 2)).toBe(0);
mapOrElse
mapOrElse<U>(this: SettledResult<T, E>, mkDef: () => Awaited<U>, f: (x: T) => Awaited<U>): U
Returns f
applied to the contained value if Ok
, otherwise returns the result of mkDef
.
- Only available on
Result
s that areSettled
. - If
f
throws, the error is silently ignored, and the result ofmkDef
is returned.
If mkDef
is called and throws an exception, ResultError
is thrown
with the original error set as ResultError.reason
.
const x = ok<number, string>(2);
const y = err<number, string>("failure");
expect(x.mapOrElse(() => 0, n => n * 2)).toBe(4);
expect(x.mapOrElse(() => 1, () => { throw new Error("boom") })).toBe(1);
expect(() => y.mapOrElse(() => { throw new Error("boom") }, n => n * 2)).toThrow(ResultError);
expect(y.mapOrElse(() => 0, n => n * 2)).toBe(0);
match
Matches this result, returning f
applied to the value if Ok
, or g
applied
to the CheckedError
if Err
.
- Only available on
Result
s that areSettled
. - If
f
org
return aPromise
that rejects, the caller is responsible for handling the rejection.
Throws ResultError
if f
or g
throws an exception,
with the original error set as ResultError.reason
.
const x = ok<number, string>(2);
const y = err<number, string>("failure");
expect(x.match(n => n * 2, () => 0)).toBe(4);
expect(() => x.match(_ => { throw new Error() }, () => 0)).toThrow(ResultError);
expect(y.match(n => n * 2, e => e.expected?.length)).toBe(7);
expect(() => y.match(n => n * 2, () => { throw new Error() })).toThrow(ResultError);
ok
Converts this result to an Option
, discarding the error if present.
Maps Ok(_)
to Some(_)
and Err(_)
to None
.
const x = ok<number, string>(2);
const y = err<number, string>("failure");
expect(x.ok()).toStrictEqual(some(2));
expect(y.ok()).toStrictEqual(none());
or
or<F>(x: Result<T, F>): Result<T, F>
Returns the current result if it is Ok
, otherwise returns x
.
const x = ok<number, string>(2);
const y = err<number, string>("failure");
expect(x.or(ok(3))).toStrictEqual(ok(2));
expect(x.or(err("failure"))).toStrictEqual(ok(2));
expect(y.or(ok(3))).toStrictEqual(ok(3));
expect(y.or(err("another one"))).toStrictEqual(err("another one"));
orElse
orElse<F>(f: () => Result<T, F>): Result<T, F>
Returns the current result if Ok
, otherwise returns the result of f
.
If f
throws, returns an Err
with an
UnexpectedError
containing the original error.
const x = ok<number, string>(2);
const y = err<number, string>("failure");
expect(x.orElse(() => ok(3))).toStrictEqual(ok(2));
expect(y.orElse(() => ok(3))).toStrictEqual(ok(3));
expect(y.orElse(() => { throw new Error("boom") }).unwrapErr().unexpected).toBeDefined();
expect(y.orElse(() => err("another one"))).toStrictEqual(err("another one"));
tap
tap(f: (x: Result<T, E>) => unknown): Result<T, E>
Executes f
with a copy of this result, then returns a new copy unchanged.
Useful for side-effects like logging, works with both Ok
and Err
.
- If
f
throws or rejects, the error is silently ignored. - If
f
returns a promise, the promise is not awaited before returning.
const x = ok<number, string>(42);
const y = err<number, string>("failure");
let log = "";
expect(x.tap(res => (log = res.toString()))).toStrictEqual(ok(42));
expect(log).toBe("Ok { 42 }");
expect(y.tap(res => (log = res.toString()))).toStrictEqual(err("failure"));
expect(log).toBe("Err { 'failure' }");
toPending
toPending(): PendingResult<Awaited<T>, Awaited<E>>
Converts this result to a PendingResult
using
a shallow copy
of its current state.
- Useful for transposing a result with a
PromiseLike
value to aPendingResult
with anAwaited
value. - If inner
T
orE
is a promise-like that rejects, maps to aPendingResult
that resolves toErr
withUnexpectedError
.
const value = { a: 1 };
const x = ok<{ a: number }, string>(value);
const pendingX = x.toPending();
expect(isPendingResult(pendingX)).toBe(true);
expect(await pendingX).toStrictEqual(ok({ a: 1 }));
value.a = 2;
expect(await pendingX).toStrictEqual(ok({ a: 2 }));
toPendingCloned
toPendingCloned(this: Result<Cloneable<T>, Cloneable<E>>): PendingResult<Awaited<T>, Awaited<E>>
Converts this result to a PendingResult
using a deep clone
of its current state.
- Useful for transposing a result with a
PromiseLike
value to aPendingResult
with anAwaited
value, preserving independence from the original data. - If inner
T
orE
is a promise-like that rejects, maps to aPendingResult
that resolves toErr
withUnexpectedError
.
class CloneableClass implements Clone<CloneableClass> {
constructor(public a: number) {}
clone(this: Clone<CloneableClass>): CloneableClass;
clone(this: CloneableClass): CloneableClass;
clone(): CloneableClass {
return new CloneableClass(this.a);
}
}
const value = new CloneableClass(0);
const x = ok<CloneableClass, number>(value);
const y = err<CloneableClass, number>(1);
const pendingX = x.toPendingCloned();
expect(isPendingResult(pendingX)).toBe(true);
expect((await pendingX).unwrap().a).toBe(0);
value.a = 42;
expect((await pendingX).unwrap().a).toBe(0);
expect(await y.toPendingCloned()).toStrictEqual(err(1));
toString
Generates a string representation of this result, reflecting its current state.
const x = ok<number, string>(2);
const y = err<number, string>("error");
expect(x.toString()).toBe("Ok { 2 }");
expect(y.toString()).toBe("Err { 'error' }");
transpose
transpose<U, F>(this: Result<Option<U>, F>): Option<Result<U, F>>
Transposes a Result
of an Option
into an Option
of a Result
.
Maps Ok(None)
to None
, Ok(Some(_))
to Some(Ok(_))
and Err(_)
to Some(Err(_))
.
const x = ok<Option<number>, string>(none());
const y = ok<Option<number>, string>(some(2));
const z = err<Option<number>, string>("error");
expect(x.transpose()).toStrictEqual(none());
expect(y.transpose()).toStrictEqual(some(ok(2)));
expect(z.transpose()).toStrictEqual(some(err("error")));
try
Extracts this result’s state, returning a tuple with a success flag, error, and value.
Inspired by the Try Operator proposal.
- Only available on
Result
s that areSettled
. - Returns
[true, undefined, T]
if this is anOk
, or[false, CheckedError<E>, undefined]
if this is anErr
. - Never throws, offering a safe way to inspect the result’s state with explicit success indication.
const x = ok<number, string>(42);
const y = err<number, string>("failure");
expect(x.try()).toEqual([true, undefined, 42]);
expect(y.try()).toEqual([false, expect.objectContaining({ expected: "failure" }), undefined]);
unwrap
unwrap(this: SettledResult<T, E>): T
Retrieves the value if this result is an Ok
, or throws a
ResultError
if it’s an Err
.
Only available on Result
s that are Settled
.
Throws ResultError
if this result is an Err
.
const x = ok<number, string>(42);
const y = err<number, string>("failure");
expect(x.unwrap()).toBe(42);
expect(() => y.unwrap()).toThrow(ResultError);
unwrapErr
unwrapErr(this: SettledResult<T, E>): CheckedError<E>
Retrieves the CheckedError
if this result is an Err
, or
throws a ResultError
if it’s an Ok
.
Only available on Result
s that are Settled
.
Throws ResultError
if this result is an Ok
.
const x = ok<number, string>(42);
const y = err<number, string>("failure");
expect(() => x.unwrapErr()).toThrow(ResultError);
expect(y.unwrapErr().expected).toBe("failure");
unwrapOr
unwrapOr(this: SettledResult<T, E>, def: Awaited<T>): T
Returns the contained value if Ok
, or def
if Err
.
Only available on Result
s that are Settled
.
const x = ok<number, string>(2);
const y = err<number, string>("failure");
expect(x.unwrapOr(0)).toBe(2);
expect(y.unwrapOr(0)).toBe(0);
unwrapOrElse
unwrapOrElse(this: SettledResult<T, E>, mkDef: () => Awaited<T>): T
Returns the contained value if Ok
, or the result of mkDef
if Err
.
Only available on Result
s that are Settled
.
Throws ResultError
if mkDef
throws, with the original error
set as ResultError.reason
.
const x = ok<number, string>(2);
const y = err<number, string>("failure");
expect(x.unwrapOrElse(() => 0)).toBe(2);
expect(y.unwrapOrElse(() => 0)).toBe(0);
expect(() => y.unwrapOrElse(() => { throw new Error("boom") })).toThrow(ResultError);