Oxide.ts
Rust's Option<T> and Result<T, E>, implemented for TypeScript.
Install / Use
/learn @traverse1984/Oxide.tsREADME
oxide.ts
Rust's Option<T> and Result<T, E>, implemented
for TypeScript. Zero dependencies, full test coverage and complete in-editor documentation.
Installation
$ npm install oxide.ts --save
New features in 1.1.0
- Added intoTuple to
Result - Added
flattentoOptionandResult
Usage
Core Features
Advanced
Importing
You can import the complete oxide.ts library:
import { Option, Some, None, Result, Ok, Err, match, Fn, _ } from "oxide.ts";
Or just the core library, which exclues the match feature:
import { Option, Some, None, Result, Ok, Err } from "oxide.ts/core";
Option
An Option represents either something, or nothing. If we hold a value of type Option<T>, we know it is either Some<T> or None. Both types share a
common API, so we can chain operations without having to worry whether we have
Some or None until pulling the value out:
import { Option, Some, None } from "oxide.ts";
function divide(x: number, by: number): Option<number> {
return by === 0 ? None : Some(x / by);
}
const val = divide(100, 20);
// Pull the value out, or throw if None:
const res: number = val.unwrap();
// Throw a custom error message in the case of None:
const res: number = val.expect("Don't divide by zero!");
// Pull the value out, or use a default if None:
const res: number = val.unwrapOr(1);
// Map the Option<T> to Option<U> by applying a function:
const strval: Option<string> = val.map((num) => `val = ${num}`);
// Unwrap the value or use a default if None:
const res: string = strval.unwrapOr("val = <none>");
// Map, assign a default and unwrap in one line:
const res: string = val.mapOr("val = <none>", (num) => `val = ${num}`);
The type annotations applied to the const variables are for information - the correct types would be inferred.
Result
A Result represents either something good (T) or something not so good (E).
If we hold a value of type Result<T, E> we know it's either Ok<T> or
Err<E>. You could think of a Result as an Option where None has a value.
import { Result, Ok, Err } from "oxide.ts";
function divide(x: number, by: number): Result<number, string> {
return by === 0 ? Err("Division by zero") : Ok(x / by);
}
const val = divide(100, 20);
// These are the same as Option (as are many of the other methods):
const res: number = val.unwrap();
const res: number = val.expect("Don't divide by zero!");
const res: number = val.unwrapOr(1);
// Map Result<T, E> to Result<U, E>
const strval: Result<string, string> = val.map((num) => `val = ${num}`);
const res: string = strval.unwrapOr("val = <err>");
const res: string = val.mapOr("val = <err>", (num) => `val = ${num}`);
// Unwrap or expect the Err (throws if the Result is Ok):
const err: string = val.unwrapErr();
const err: string = val.expectErr("Expected division by zero!");
// Or map the Err, converting Result<T, E> to Result<T, F>
const errobj: Result<string, Error> = val.mapErr((msg) => new Error(msg));
Converting
These methods provide a way to jump in to (and out of) Option and Result
types. Particularly these methods can streamline things where:
- A function returns
T | null,T | falseor similar. - You are working with physical quantities or using an
indexOfmethod. - A function accepts an optional argument,
T | nullor similar.
Note: Converting to a Result often leaves you with a Result<T, null>.
The null value here is not very useful - consider the equivalent Option method
to create an Option<T>, or use mapErr to change the E type.
into
Convert an existing Option/Result into a union type containing T and
undefined (or a provided falsey value).
function maybeName(): Option<string>;
function maybeNumbers(): Result<number[], Error>;
function printOut(msg?: string): void;
const name: string | undefined = maybeName().into();
const name: string | null = maybeName().into(null);
// Note that the into type does not reflect the E type:
const numbers: number[] | undefined = maybeNumbers().into();
const numbers: number[] | false = maybeNumbers().into(false);
// As a function argument:
printOut(name.into());
intoTuple
Convert a Result<T, E> into a tuple of [null, T] if the result is Ok,
or [E, null] otherwise.
function getUsername(): Result<string, Error>;
const query = getUsername();
const [err, res] = query.intoTuple();
if (err) {
console.error(`Query Error: ${err}`);
} else {
console.log(`Welcome: ${res.toLowerCase()}`);
}
from
Convert to an Option/Result which is Some<T>/Ok<T> unless the value is
falsey, an instance of Error or an invalid Date.
The T is narrowed to exclude any falsey values or Errors.
const people = ["Fry", "Leela", "Bender"];
// Create an Option<string> from a find:
const person = Option.from(people.find((name) => name === "Fry"));
// or shorter:
const person = Option(people.find((name) => name === "Bender"));
In the case of Result, the E type includes:
null(ifvalcould have been falsey or an invalid date)Errortypes excluded fromT(if there are any)
function randomName(): string | false;
function tryName(): string | Error;
function randomNumbers(): number[] | Error;
// Create a Result<string, null>
const person = Result.from(randomName());
// Create a Result<string, Error | null>
const name = Result(tryName());
// Create a Result<number[], Error>
const num = Result(randomNumbers());
nonNull
Convert to an Option/Result which is Some<T>/Ok<T> unless the value
provided is undefined, null or NaN.
function getNum(): number | null;
const num = Option.nonNull(getNum()).unwrapOr(100); // Could be 0
const words = ["express", "", "planet"];
const str = Option.nonNull(words[getNum()]);
str.unwrapOr("No such index"); // Could be ""
qty
Convert to an Option/Result which is Some<number>/Ok<number>
when the provided val is a finite integer greater than or equal to 0.
const word = "Buggalo";
const g = Option.qty(word.indexOf("g"));
assert.equal(g.unwrap(), 2);
const z = Option.qty(word.indexOf("z"));
assert.equal(z.isNone(), true);
Nesting
You can nest Option and Result structures. The following example uses
nesting to distinguish between found something, found nothing and
database error:
function search(query: string): Result<Option<SearchResult>, string> {
const [err, result] = database.search(query);
if (err) {
return Err(err);
} else {
return Ok(result.count > 0 ? Some(result) : None);
}
}
const result = search("testing");
const output: string = match(result, {
Ok: {
Some: (result) => `Found ${result.count} entries.`,
None: () => "No results for that search.",
},
Err: (err) => `Error: ${err}.`,
});
Iteration
An Option or Result that contains an iterable T type can be iterated upon
directly. In the case of None or Err, an empty iterator is returned.
The compiler will complain if the inner type is not definitely iterable
(including any), or if the monad is known to be None or Err.
const numbers = Option([1.12, 2.23, 3.34]);
for (const num of numbers) {
console.log("Number is:", num.toFixed(1));
}
const numbers: Option<number[]> = None;
for (const num of numbers) {
console.log("Unreachable:", num.toFixed());
}
It's also possible to iterate over nested monads in the same way:
const numbers = Option(Result(Option([1, 2, 3])));
for (const num of numbers) {
console.log("Number is:", num.toFixed(1));
}
Safe
Capture the outcome of a function or Promise as an Option<T> or
Result<T, E>, preventing throwing (function) or rejection (Promise).
Safe Functions
Calls the passed function with the arguments provided and returns an
Option<T> or Result<T, Error>. The outcome is Some/Ok if the function
returned, or None/Err if it threw. In the case of Result.safe, any thrown
value which is not an Error is converted.
function mightThrow(throws: boolean) {
if (throws) {
throw new Error("Throw");
}
return "Hello World";
}
const x: Result<string, Error> = Result.safe(mightThrow, true);
assert.equal(x.unwrapErr() instanceof Error, true);
assert.equal(x.unwrapErr().message, "Throw");
const x = Result.safe(() => mightThrow(false));
assert.equal(x.unwrap(), "Hello World");
Note: Any function which returns a Promise (or PromiseLike) value is
rejected by the type signature. Result<Promise<T>, Error> or
Option<Promise<T>> are not useful types - using it in this way is likely
to be a mistake.
Safe Promises
Accepts a Promise and returns a new Promise which always resolves to either
an Option<T> or Result<T, Error>. The Result is Some/Ok if the original
promise resolved, or None/Err if it rejected. In the case of Result.safe,
any rejection value which is not an Error is converted.
async function mightThrow(throws: boolean) {
if (throws) {
throw new Error("Throw");
}
return "Hello World";
}
const x = await Result.safe(mightThrow(true));
assert.equal(x.unwrapErr() instanceof Error, true);
assert.equal(x.unwrapErr().message, "Throw");
const x = await Result.safe(mightThrow(false));
assert.equal(x.unwrap(), "Hello World");
All
Reduce multiple Options or Results to a single one. The first None
Related Skills
node-connect
345.4kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
104.6kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
345.4kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
345.4kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
