Skip to main content

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");
}
info

functions runResult, ok, err as well as type Result are imported from @ts-rust/std package.

warning

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.

note

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

Constructors

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.

note
  • Only available on Results that are Settled.
  • Returns [true, T] if this is an Ok, or [false, CheckedError<E>] if this is an Err.
  • 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.

note

Only available on Results 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

copy(): Result<T, E>

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.

note
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.

note

Only available on Results that are Settled.

danger

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.

note

Only available on Results that are Settled.

danger

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>).

note

Only available on Results 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.

note
  • Returns a new Result instance, not the original reference.
  • If f throws or returns a Promise 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.

note
  • Returns a new Result instance, not the original reference.
  • If f throws or returns a Promise 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

isErr(): this is Err<T, E>

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.

note

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

isOk(): this is Ok<T, E>

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.

note

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.

note
  • Yields exactly one item for Ok, or zero items for Err.
  • 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.

note

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

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.

note

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.

note
  • If f throws, returns an Err with an UnexpectedError containing the original error.
  • If this is an Err with an UnexpectedError, 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.

note
  • Only available on Results that are Settled.
  • f has to return a synchronous (Awaited) value.
  • If f throws, returns def.
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.

note
  • Only available on Results that are Settled.
  • If f throws, the error is silently ignored, and the result of mkDef is returned.
danger

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

match<U, F = U>(this: SettledResult<T, E>, f: (x: T) => Awaited<U>, g: (e: CheckedError<E>) => Awaited<F>): U | F

Matches this result, returning f applied to the value if Ok, or g applied to the CheckedError if Err.

note
  • Only available on Results that are Settled.
  • If f or g return a Promise that rejects, the caller is responsible for handling the rejection.
danger

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

ok(): Option<T>

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.

note

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.

note
  • 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.

note
  • Useful for transposing a result with a PromiseLike value to a PendingResult with an Awaited value.
  • If inner T or E is a promise-like that rejects, maps to a PendingResult that resolves to Err with UnexpectedError.
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.

note
  • Useful for transposing a result with a PromiseLike value to a PendingResult with an Awaited value, preserving independence from the original data.
  • If inner T or E is a promise-like that rejects, maps to a PendingResult that resolves to Err with UnexpectedError.
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

toString(): string

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

try(this: SettledResult<T, E>): this extends Ok<T, E> ? [true, undefined, T] : [false, CheckedError<E>, undefined]

Extracts this result’s state, returning a tuple with a success flag, error, and value.

Inspired by the Try Operator proposal.

note
  • Only available on Results that are Settled.
  • Returns [true, undefined, T] if this is an Ok, or [false, CheckedError<E>, undefined] if this is an Err.
  • 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.

note

Only available on Results that are Settled.

danger

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.

note

Only available on Results that are Settled.

danger

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.

note

Only available on Results 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.

note

Only available on Results that are Settled.

danger

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);