Let’s talk about what’s wrong with JavaScript error handling. Here’s a function:
function getUser(id: number): User | null {
// ...
}
The caller has to remember to null-check. The type system nudges them, but there’s nothing stopping this:
const user = getUser(42);
console.log(user.name); // TypeError at runtime if user is null
Or this pattern, which is even worse:
async function fetchConfig(): Promise<Config> {
// can throw network error, parse error, validation error...
// none of these appear in the type signature
}
The errors are invisible. The caller doesn’t know what to handle.
There’s a better model
Rust has Option for values that might not exist, and Result for operations that can fail. Both are explicit in the type signature. Both force the caller to handle every case.
@rslike/std brings this to TypeScript.
npm i @rslike/std
Option — make absence impossible to ignore
import { Some, None, Option, match } from "@rslike/std";
function findUser(id: number): Option<User> {
const user = db.find(u => u.id === id);
return user ? Some(user) : None();
}
Now the caller must handle both states. They can’t accidentally treat None as a value:
const opt = findUser(42);
// Safe extraction with fallback
const user = opt.unwrapOr(guestUser);
// Pattern matching — handles both branches exhaustively
const greeting = match(
opt,
(user) => `Hello, ${user.name}!`,
() => "Hello, guest!"
);
Transform without unwrapping
const displayName = findUser(42)
.map(u => `${u.firstName} ${u.lastName}`)
.unwrapOr("Unknown User");
// Chain operations that also return Option
const avatar = findUser(42)
.flatMap(u => findAvatar(u.avatarId))
.unwrapOr(defaultAvatar);
Quick checks
const opt = Some("hello");
opt.isSome(); // true
opt.isNone(); // false
opt.unwrap(); // "hello"
const empty = None();
empty.isNone(); // true
empty.unwrapOr("fallback"); // "fallback"
empty.unwrap(); // throws UndefinedBehaviorError — intentional!
Result — make errors part of the contract
import { Ok, Err, Result, match } from "@rslike/std";
function divide(a: number, b: number): Result<number, string> {
return new Result((ok, err) => {
if (b === 0) {
err("Division by zero");
} else {
ok(a / b);
}
});
}
The error type is in the signature. Callers know what to expect:
const r = divide(10, 2);
r.isOk(); // true
r.unwrap(); // 5
const bad = divide(10, 0);
bad.isErr(); // true
bad.unwrapErr(); // "Division by zero"
bad.unwrapOr(0); // 0
Wrapping existing code that throws
function parseJSON(raw: string): Result<unknown, SyntaxError> {
return new Result((ok, err) => {
try {
ok(JSON.parse(raw));
} catch (e) {
err(e as SyntaxError);
}
});
}
const config = parseJSON(rawInput)
.map(data => validate(data))
.mapErr(e => `Invalid config: ${e.message}`)
.unwrapOr(defaults);
Pattern matching
const message = match(
parseJSON(rawInput),
(data) => `Loaded: ${JSON.stringify(data)}`,
(err) => `Error: ${err.message}`
);
match — exhaustive two-branch dispatch
match works with Option, Result, and boolean:
import { match } from "@rslike/std";
// boolean
match(isAdmin, (t) => "admin panel", (f) => "dashboard");
// Option
match(someOption, (value) => `Got: ${value}`, () => "nothing");
// Result
match(someResult, (n) => n * 2, (e) => -1);
TypeScript infers the callback parameter types from the input — you can’t accidentally use the error handler as the success handler.
Globals — skip the imports
// entry.ts — once
import "@rslike/std/globals";
// anywhere else in your app — no imports needed
const x = Some(42);
const r = Ok("success");
const n = None();
const e = Err(new Error("oops"));
The real benefit
When Option and Result are in your function signatures, code review becomes a conversation about intent rather than a hunt for unhandled edge cases.
// Before: what does null mean here? forgotten value? intentional absence?
function getSession(token: string): Session | null
// After: clear contract — either a session or nothing
function getSession(token: string): Option<Session>
// Before: can this throw? which errors?
async function createOrder(cart: Cart): Promise<Order>
// After: explicit failure type in the signature
async function createOrder(cart: Cart): Promise<Result<Order, OrderError>>
Install
npm i @rslike/std
Source: github.com/vitalics/rslike
Have you used Option/Result patterns in TypeScript before? What library are you using? Let me know below.