Rust's Option, Result, and sync primitives for JavaScript/TypeScript - Better error handling and null-safety patterns.
- Option<T> - Represents an optional value: every
Optionis eitherSome(T)orNone - Result<T, E> - Represents either success (
Ok(T)) or failure (Err(E)) - Sync Primitives - Rust-inspired
Once<T>,Lazy<T>,LazyAsync<T>, andMutex<T> - Control Flow -
ControlFlow<B, C>withBreakandContinuefor short-circuiting operations - Full TypeScript support with strict type inference
- Async support - Async versions of all transformation methods
- Zero dependencies
- Runtime immutability - All instances are frozen with
Object.freeze() - Cross-runtime - Works in Node.js, Deno, Bun, and browsers
# npm
npm install happy-rusty
# yarn
yarn add happy-rusty
# pnpm
pnpm add happy-rusty
# JSR (Deno)
deno add @happy-js/happy-rusty
# JSR (Bun)
bunx jsr add @happy-js/happy-rustyimport { Some, None, Ok, Err } from 'happy-rusty';
// Option - handling nullable values
function findUser(id: number): Option<User> {
const user = database.get(id);
return user ? Some(user) : None;
}
const user = findUser(1)
.map(u => u.name)
.unwrapOr('Guest');
// Result - handling errors
function parseJSON<T>(json: string): Result<T, Error> {
try {
return Ok(JSON.parse(json));
} catch (e) {
return Err(e as Error);
}
}
const config = parseJSON<Config>(jsonStr)
.map(c => c.settings)
.unwrapOrElse(err => {
console.error('Parse failed:', err);
return defaultSettings;
});| Category | Methods |
|---|---|
| Constructors | Some(value), None |
| Querying | isSome(), isNone(), isSomeAnd(fn) |
| Extracting | expect(msg), unwrap(), unwrapOr(default), unwrapOrElse(fn) |
| Transforming | map(fn), mapOr(default, fn), mapOrElse(defaultFn, fn), filter(fn), flatten() |
| Boolean ops | and(other), andThen(fn), or(other), orElse(fn), xor(other) |
| Converting | okOr(err), okOrElse(fn), transpose() |
| Combining | zip(other), zipWith(other, fn), unzip() |
| Side effects | inspect(fn) |
| Comparison | eq(other) |
| Category | Methods |
|---|---|
| Constructors | Ok(value), Ok() (void), Err(error) |
| Querying | isOk(), isErr(), isOkAnd(fn), isErrAnd(fn) |
| Extracting Ok | expect(msg), unwrap(), unwrapOr(default), unwrapOrElse(fn) |
| Extracting Err | expectErr(msg), unwrapErr() |
| Transforming | map(fn), mapErr(fn), mapOr(default, fn), mapOrElse(defaultFn, fn), flatten() |
| Boolean ops | and(other), andThen(fn), or(other), orElse(fn) |
| Converting | ok(), err(), transpose() |
| Type casting | asOk<F>(), asErr<U>() |
| Side effects | inspect(fn), inspectErr(fn) |
| Comparison | eq(other) |
All transformation methods have async variants with Async suffix:
// Async Option methods
isSomeAndAsync(asyncFn)
unwrapOrElseAsync(asyncFn)
andThenAsync(asyncFn)
orElseAsync(asyncFn)
// Async Result methods
isOkAndAsync(asyncFn)
isErrAndAsync(asyncFn)
unwrapOrElseAsync(asyncFn)
andThenAsync(asyncFn)
orElseAsync(asyncFn)// Convenient type aliases for common patterns
type AsyncOption<T> = Promise<Option<T>>;
type AsyncResult<T, E> = Promise<Result<T, E>>;
// For I/O operations
type IOResult<T> = Result<T, Error>;
type AsyncIOResult<T> = Promise<IOResult<T>>;
// For void returns
type VoidResult<E> = Result<void, E>;
type VoidIOResult = IOResult<void>;
type AsyncVoidResult<E> = Promise<VoidResult<E>>;
type AsyncVoidIOResult = Promise<VoidIOResult>;import { isOption, isResult, isControlFlow, promiseToAsyncResult } from 'happy-rusty';
// Type guards
if (isOption(value)) { /* ... */ }
if (isResult(value)) { /* ... */ }
if (isControlFlow(value)) { /* ... */ }
// Convert Promise to Result
const result = await promiseToAsyncResult(fetch('/api/data'));
result.inspect(data => console.log(data))
.inspectErr(err => console.error(err));import { RESULT_TRUE, RESULT_FALSE, RESULT_ZERO, RESULT_VOID } from 'happy-rusty';
// Reusable immutable Result constants
function validate(): Result<boolean, Error> {
return isValid ? RESULT_TRUE : RESULT_FALSE;
}
function doSomething(): Result<void, Error> {
// ...
return RESULT_VOID;
}import { Once, Lazy, LazyAsync, Mutex } from 'happy-rusty';
// Once - one-time initialization (like Rust's OnceLock)
const config = Once<Config>();
config.set(loadConfig()); // Set once
config.get(); // Some(config) or None
config.getOrInit(() => defaultCfg); // Get or initialize
// Lazy - lazy initialization with initializer at construction
const expensive = Lazy(() => computeExpensiveValue());
expensive.force(); // Compute on first access, cached thereafter
// LazyAsync - async lazy initialization
const db = LazyAsync(async () => await Database.connect(url));
await db.force(); // Only one connection, concurrent calls wait
// Mutex - async mutual exclusion
const state = Mutex({ count: 0 });
await state.withLock(async (s) => {
s.count += 1; // Exclusive access
});import { Break, Continue, ControlFlow } from 'happy-rusty';
// Short-circuit operations
function findFirst<T>(arr: T[], pred: (t: T) => boolean): Option<T> {
for (const item of arr) {
const flow = pred(item) ? Break(item) : Continue();
if (flow.isBreak()) {
return Some(flow.breakValue().unwrap());
}
}
return None;
}
// Custom fold with early exit
function tryFold<T, Acc>(
arr: T[],
init: Acc,
f: (acc: Acc, item: T) => ControlFlow<Acc, Acc>
): Acc {
let acc = init;
for (const item of arr) {
const flow = f(acc, item);
if (flow.isBreak()) return flow.breakValue().unwrap();
acc = flow.continueValue().unwrap();
}
return acc;
}Full API documentation is available at jiangjie.github.io/happy-rusty.
All types (Option, Result, ControlFlow, Lazy, LazyAsync, Once, Mutex, MutexGuard) are immutable at runtime via Object.freeze(). This prevents accidental modification of methods or properties:
const some = Some(42);
some.unwrap = () => 0; // TypeError: Cannot assign to read only propertyWhy no readonly in TypeScript interfaces?
We intentionally omit readonly modifiers from method signatures in interfaces. While this might seem to reduce type safety, there are compelling reasons:
-
Inheritance compatibility - The
Nonetype extendsOption<never>. TypeScript's arrow function property syntax (readonly prop: () => T) uses contravariant parameter checking, which causesNone(withneverparameters) to be incompatible withOption<T>. Method syntax (method(): T) uses bivariant checking, allowing the inheritance to work correctly. -
Runtime protection is sufficient -
Object.freeze()already prevents reassignment at runtime. Addingreadonlyonly provides compile-time checking, which offers marginal benefit when runtime protection exists. -
Cleaner API - Avoiding the
Mutable*+Readonly<>pattern keeps the exported types clean and documentation readable. -
Testing validates immutability - Our test suite explicitly verifies that all instances are frozen and reject property modifications.
JavaScript's null/undefined and try-catch patterns lead to:
- Uncaught null reference errors
- Forgotten error handling
- Verbose try-catch blocks
- Unclear function contracts
happy-rusty provides Rust's battle-tested patterns:
- Explicit optionality -
Option<T>makes absence visible in types - Explicit errors -
Result<T, E>forces error handling consideration - Method chaining - Transform values without nested if-else or try-catch
- Type safety - Full TypeScript support with strict type inference