Skip to content

Latest commit

 

History

History
189 lines (138 loc) · 5.18 KB

README.md

File metadata and controls

189 lines (138 loc) · 5.18 KB

PoC TypeScript Option and Result type

This project implements basic versions of the Option and Result type, inspired by their counterparts in the Rust programming language, to demonstrate their benefits in TypeScript.

Motivation

In Rust, the Option type is used to represent the presence or absence of a value. The Result type, on the other hand, is designed for error handling, avoiding exceptions by encouraging explicit handling of both success and failure.

While TypeScript supports truthiness narrowing to determine if a value is null or undefined, making the Option type less crucial, the Result type is particularly valuable. It allows developers to represent errors directly in the return type, promoting safer and more predictable error handling by leveraging TypeScript's type system to enforce explicit handling of both success and failure cases, rather than depending on unchecked exceptions.

Overview

The Option type represent an optional value, either Some (a value is present) or None (no value). It removes ambiguity and is safer than relying on null or undefined.

Example:

import { type Option, Some, None } from "./option";

interface User {
    id: number;
    name: string;
    address?: string;
}

const users: User[] = [
    { id: 1, name: "Alice", address: "123 Main St" },
    { id: 2, name: "Bob" },
    { id: 3, name: "Charlie", address: "456 Oak St" },
];

function getUserAddress(userId: number): Option<string> {
    const user = users.find((u) => u.id === userId);
    return user && user.address ? Some(user.address) : None;
}

const opt = getUserAddress(1);
// const opt = getUserAddress(2);
// const opt = getUserAddress(4);

if (opt.isSome()) {
    // infered string
    console.log(opt.unwrap());
} else {
    // infered never
    console.log("No value");
}

The Result type represents the outcome of an operation that can succeed or fail, either Ok (success with a value) or Err (failure with an error). It avoids throwing exceptions and provides a functional approach to error handling.

Example:

import { type Result, Ok, Err } from "./result";

function divide(dividend: number, divisor: number): Result<number, Error> {
    if (divisor === 0) {
        return Err(new Error("Cannot divide by zero"));
    } else {
        return Ok(dividend / divisor);
    }
}

const res = divide(10, 2);
// const res = divide(10, 0);

if (res.isOk()) {
    // infered number
    console.log(res.unwrap());
} else if (res.isErr()) {
    // infered Error
    console.error(res.unwrapErr().message);
}

Handling exceptions with wrapException

The wrapException utility helps integrate Result with functions that throw exceptions. This works for both synchronous and asynchronous operations.

Synchronous Example:

import { type Result } from "./result";
import wrapException from "./wrapException";

const thisThrows = () => {
    throw new Error("Something went wrong");
    return 42;
};

const res = wrapException(() => thisThrows());

if (res.isOk()) {
    // infered number
    console.log(res.unwrap());
} else if (res.isErr()) {
    // infered any
    console.error(res.unwrapErr().message);
}

Asynchronous Example:

import { type Result } from "./result";
import wrapException from "./wrapException";

const thisThrowsAsync = async () => {
    throw new Error("Async failure");

    return await Promise.resolve(1);
};

const res = await wrapException(async () => thisThrowsAsync());

if (res.isOk()) {
    // infered number
    console.log(res.unwrap());
} else if (res.isErr()) {
    // infered any
    console.error(res.unwrapErr().message);
}

Typed variant of wrapException

You can explicitly specify types for the success and error cases.

Synchronous Example:

import { type Result } from "./result";
import wrapException from "./wrapException";

const thisThrows = () => {
    throw new Error("Something went wrong");

    return 1;
};

const res = wrapException<number, Error>(() => thisThrows());

if (res.isOk()) {
    // infered number
    console.log(res.unwrap());
} else if (res.isErr()) {
    // infered Error
    console.error(res.unwrapErr().message);
}

Asynchronous Example:

import { type Result } from "./result";
import wrapException from "./wrapException";

const thisThrowsAsync = async () => {
    throw new Error("throwing async");

    return await Promise.resolve(1);
};

const res = await wrapException<ReturnType<typeof thisThrowsAsync>, Error>(
    async () => thisThrowsAsync(),
);

if (res.isOk()) {
    // infered number
    console.log(res.unwrap());
} else if (res.isErr()) {
    // infered Error
    console.error(res.unwrapErr().message);
}

License

See the LICENSE.