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<T> for values that might not exist, and Result<T, E> 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<string>
match(someOption, (value) => `Got: ${value}`, () => "nothing");
// Result<number, Error>
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.
Top comments (0)