Skip to content

Instantly share code, notes, and snippets.

@thomastheyoung
Last active August 1, 2025 05:02
Show Gist options
  • Save thomastheyoung/e88627ac6e7f94c560baea726cee98e5 to your computer and use it in GitHub Desktop.
Save thomastheyoung/e88627ac6e7f94c560baea726cee98e5 to your computer and use it in GitHub Desktop.
Rust-inspire Result for Typescript: A tri-state Result type that represents operations that can succeed with a value, fail with an error, or return empty.
/**
* Result Type System
*
* A tri-state Result type that represents operations that can succeed with a value,
* fail with an error, or return empty. This pattern replaces traditional try/catch
* with explicit error handling and null safety.
*
* @template T - The type of successful values
* @template E - The type of error values
*
* **Why use Result?**
* - Eliminates null/undefined bugs by making empty states explicit
* - Forces error handling at compile time through type checking
* - Provides a consistent API for operations that may fail or return nothing
* - Enables functional programming patterns with map/match operations
*
* **Use cases:**
* - API calls that may fail or return no data
* - Database queries that may not find records
* - Parsing operations that may encounter invalid input
* - File operations that may fail or find no files
*/
export type Result<T, E = Error> =
| { readonly type: 'some'; readonly value: T }
| { readonly type: 'none' }
| { readonly type: 'err'; readonly error: E };
/** Async version of Result for operations that return promises */
export type AsyncResult<T, E = Error> = Promise<Result<T, E>>;
/**
* Constructors - Create Result instances
*
* These functions create the three possible states of a Result.
* Use these instead of manually constructing Result objects.
*/
/** Creates a successful Result containing a value */
export const some = <T, E = never>(value: T): Result<T, E> => ({ type: 'some', value });
/** Creates an empty Result (no value, no error) */
export const none = <T, E = never>(): Result<T, E> => ({ type: 'none' });
/** Creates a failed Result containing an error */
export const err = <T = never, E = unknown>(error: E): Result<T, E> => ({ type: 'err', error });
/** Creates a successful Result without a value (for binary success/failure) */
export const ok = <E = never>(): Result<true, E> => ({ type: 'some', value: true });
/**
* Type Guards - Safely check and narrow Result types
*
* These functions determine which variant of Result you have and provide
* type-safe access to the contained value or error.
*/
/** Checks if Result contains a successful value */
export function isSome<T, E>(res: Result<T, E>): res is { type: 'some'; value: T } {
return res.type === 'some';
}
/** Checks if Result is empty (no value, no error) */
export function isNone<T, E>(res: Result<T, E>): res is { type: 'none' } {
return res.type === 'none';
}
/** Checks if Result contains an error */
export function isErr<T, E>(res: Result<T, E>): res is { type: 'err'; error: E } {
return res.type === 'err';
}
/**
* Transformation Functions - Transform Results without unwrapping
*
* These functions let you work with the contained values while preserving
* the Result wrapper and error handling semantics.
*/
/** Transforms the success value while preserving error and empty states */
export function map<T, U, E>(res: Result<T, E>, fn: (t: T) => U): Result<U, E> {
if (isSome(res)) {
return some(fn(res.value));
}
if (isErr(res)) {
return res as Result<U, E>;
}
return none();
}
/** Transforms the error value while preserving success and empty states */
export function mapErr<T, E, F>(res: Result<T, E>, fn: (e: E) => F): Result<T, F> {
if (isErr(res)) {
return err(fn(res.error));
}
return res as Result<T, F>;
}
/**
* Extraction Functions - Get values out of Results safely
*
* These functions provide safe ways to extract values from Results
* with appropriate fallbacks for error and empty cases.
*/
/** Extracts the value if successful, otherwise returns the fallback */
export function unwrapOr<T, E>(res: Result<T, E>, fallback: T): T {
return isSome(res) ? res.value : fallback;
}
/**
* Pattern Matching - Handle all Result cases explicitly
*
* These functions provide exhaustive handling of all possible Result states.
* The compiler ensures you handle every case, preventing runtime errors.
*/
/** Handles all three Result cases with provided functions */
export function match<T, E, U>(
res: Result<T, E>,
handlers: {
some: (t: T) => U;
none: () => U;
err: (e: E) => U;
},
): U {
if (isSome(res)) return handlers.some(res.value);
if (isErr(res)) return handlers.err(res.error);
return handlers.none();
}
/** Handles error and empty cases, returns value directly if successful */
export function matchOr<T, E, U>(
res: Result<T, E>,
handlers: {
none: () => U;
err: (e: E) => U;
},
): T | U {
if (isSome(res)) return res.value;
if (isErr(res)) return handlers.err(res.error);
return handlers.none();
}
/**
* Assertion Functions - Force unwrapping with runtime checks
*
* Use sparingly when you're certain a Result contains a value.
* Throws errors for empty or error states.
*/
/** Asserts that Result contains a value, throws if empty or error */
export function assertSome<T, E>(
res: Result<T, E>,
message?: string,
): asserts res is { type: 'some'; value: T } {
if (isNone(res)) {
throw new Error(message || 'Empty result');
} else if (isErr(res)) {
throw new Error(message || 'Error while trying to fetch result');
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment