Rsult
Bring the power of Rust's error handling to TypeScript with rsult. Handle optional values and results elegantly, reduce null checks, and write safer, more expressive code.
Install / Use
/learn @19h/RsultREADME
$ pnpm add rsult
import { Option, Result, Some, None, Ok, Err } from 'rsult';
// Async variants for Promise-based workflows
import { ResultAsync, OptionAsync } from 'rsult';
tl;dr
- rsult is inspired by Rust's
OptionandResulttypes. - It helps you handle optional values and results, eliminating
nullandundefinedchecks. - You can wrap values in
Some,None,Ok, orErr, and use handy functions to transform, combine, and handle errors expressively. - Includes async variants (
ResultAsync,OptionAsync) for seamless Promise-based workflows. - Uses branded types for nominal typing and proper TypeScript type safety.
Usage
Option
The Option type is used for values that may or may not be present. It can be either Some or None.
Creating an Option
const someValue: Option<number> = Some(5);
const noneValue: Option<number> = None();
Checking if an Option is Some or None
if (someValue.is_some()) {
console.log("It's Some!");
}
if (noneValue.is_none()) {
console.log("It's None!");
}
Transforming the Value Inside an Option
const transformedValue = someValue.map(x => x * 2); // Some(10)
Handling Options with Default Values
const valueWithDefault = noneValue.unwrap_or(0); // 0
Result
The Result type is used for operations that can succeed or fail. It can be either Ok or Err.
Creating a Result
const okResult: Result<number, string> = Ok(5);
const errResult: Result<number, string> = Err("An error occurred");
Checking if a Result is Ok or Err
if (okResult.is_ok()) {
console.log("It's Ok!");
}
if (errResult.is_err()) {
console.log("It's Err!");
}
Transforming the Value Inside a Result
const transformedResult = okResult.map(x => x * 2); // Ok(10)
Handling Results with Default Values
const valueWithDefault = errResult.unwrap_or(0); // 0
Async Variants
rsult provides ResultAsync and OptionAsync for working with Promises in a type-safe, composable way.
ResultAsync
import { ResultAsync } from 'rsult';
// Create from various sources
const fromPromise = ResultAsync.fromPromise(fetch('/api/data'));
const fromTry = ResultAsync.try(async () => {
const response = await fetch('/api/data');
return response.json();
});
// Chain async operations
const result = await ResultAsync.ok(userId)
.andThen(id => fetchUser(id))
.map(user => user.name)
.mapErr(err => `Failed: ${err.message}`);
// Combine multiple async results
const combined = await ResultAsync.all([
fetchUser(1),
fetchUser(2),
fetchUser(3),
]);
OptionAsync
import { OptionAsync } from 'rsult';
// Create from various sources
const fromNullable = OptionAsync.fromNullable(maybeValue);
const fromPromise = OptionAsync.fromPromise(fetchOptionalData());
// Chain async operations
const result = await OptionAsync.some(userId)
.andThen(id => findUser(id))
.map(user => user.email)
.filter(email => email.endsWith('@example.com'));
Converting Between Sync and Async
// Sync to Async
const asyncResult = Ok(42).toAsync();
const asyncOption = Some('hello').toAsync();
// Async resolves to sync types
const syncResult: Result<number, Error> = await asyncResult;
const syncOption: Option<string> = await asyncOption;
Advanced Usage
Advanced Usage: Option
Advanced Option Transformations
Applying multiple transformations consecutively demonstrates the power of composable operations.
const option = Some(10);
const transform = option
.map(x => x * 2)
.and_then(x => x > 15 ? Some(x) : None())
.unwrap_or(0);
console.log(transform); // 20
This example showcases converting a numeric option to a string if it meets a condition, providing a default otherwise.
Combining Multiple Options
When dealing with multiple optional values, Option can elegantly handle combinations, making sure all values are present.
const option1: Option<number> = Some(10);
const option2: Option<string> = Some("twenty");
const combinedOption = option1.and_then(num =>
option2.map(str => `${num} and ${str}`)
);
console.log(combinedOption.unwrap_or("Missing value")); // "10 and twenty"
This demonstrates combining numerical and string options into a single descriptive string if both are present.
Filtering and Conditional Access
Filter out options that don't satisfy a certain condition, effectively allowing conditional access to Some values.
const numberOption: Option<number> = Some(42);
const filteredOption = numberOption.filter(x => x > 100);
console.log(filteredOption.is_none()); // true
Only values satisfying the condition remain, others turn into None.
Advanced Usage: Result
Chaining Result Operations
By chaining operations, you can handle complex data manipulation and error handling with ease.
const processResult: Result<number, string> = Ok(5);
const chainedResult = processResult.map(x => x * 2)
.and_then(x => x > 5 ? Ok(x.toString()) : Err("Value too small"))
.map_err(err => `Error encountered: ${err}`);
console.log(chainedResult.unwrap_or("Default value")); // "10"
This transformation sequence demonstrates error handling and conditional mapping in a powerful, readable manner.
Error Recovery
Perform error recovery by providing alternative workflows in case of errors.
enum ErrorType {
NotFound,
Invalid,
Unrecoverable,
}
const riskyOperation: Result<number, ErrorType> = Err(ErrorType.NotFound);
const recoveryAttempt = riskyOperation.or_else(err =>
err !== ErrorType.Unrecoverable ? Ok(0) : Err("Unrecoverable error")
);
console.log(recoveryAttempt.unwrap()); // 0
This example shows a simple mechanism for recovering from specific errors, providing a fallback result.
Combining Results with Different Types
Use case-driven transformations to work with results of varying types, demonstrating flexibility in handling operations that might fail.
const fetchResource: () => Result<string, Error> = () => Ok("Resource content");
const parseResource: (content: string) => Result<object, string> = content =>
content.length > 0 ? Ok({ parsed: content }) : Err("Empty content");
const result = fetchResource()
.and_then(parseResource)
.map(parsed => `Parsed content: ${JSON.stringify(parsed)}`)
.unwrap_or("Default content");
console.log(result); // "Parsed content: {"parsed":"Resource content"}"
API Reference
Option
Check Methods
is_some(): Checks if the Option is Some.is_none(): Checks if the Option is None.is_some_and(f: (arg: T) => boolean): Determines if the Option is Some and the contained value meets a condition.is_none_or(f: (arg: T) => boolean): Returnstrueif the Option is None, or if Some and the value satisfies the predicate.
Transform Methods
map(fn: (arg: T) => U): Transforms the contained value of a Some with a provided function. Returns None if this Option is None.map_or<U>(defaultVal: U, fn: (arg: T) => U): Applies a function to the contained value if Some, otherwise returns a provided default.
Expect and Unwrap Methods
expect(msg: string): Extracts the value from a Some, throwing an error if it is None.unwrap(): Unwraps the Option, returning the contained value, or throws an error if the Option is None.unwrap_or(defaultVal: T): Returns the contained value if Some, else returns a provided alternative.unwrap_or_else(fn: () => T): Returns the contained value if Some, else computes a value from a provided function.unwrap_or_default(): Returns the contained value if Some, otherwise the default value for the type.
Combine Methods
and<U>(opt: Option<U>): Returns the passed Option if this Option is Some, else returns None.and_then<U>(fn: (arg: T) => Option<U>): Returns the result of applying a function to the contained value if Some, otherwise returns None.or<U>(opt: Option<U>): Returns the passed Option if this Option is None, else returns this Option.or_else<U>(fn: () => Option<U>): Returns the result of applying a function if this Option is None, else returns this Option.xor(optb: Option<T>): Returns None if both this and the passed Option are Some. Otherwise returns the Option that is Some.
Mutate Methods
take(): Takes the contained value out of the Option, leaving a None in its place.take_if(predicate: (arg: T) => boolean): Takes the contained value out of the Option if it satisfies a predicate, leaving a None in its place.replace(value: T): Replaces the contained value with another, returning the old value wrapped in an Option.
Zip Methods
zip<U>(other: Option<U>): Combines two Option values into a single Option containing a tuple of their values if both are Some, otherwise returns None.zip_with<U, R>(other: Option<U>, f: (val: T, other: U) => R): Combines two Option values by applying a function if both are Some, otherwise returns None.
Filter Method
filter(predicate: (arg: T) => boolean): Applies a predicate to the contained value if Some, returns None if the predicate does not h
