DEV Community

veerakumarak
veerakumarak

Posted on

Bringing Rust & Go-Inspired Functional Error Handling to TypeScript

Functional programming isn’t just for Haskell or Scala anymore — with TypeScript’s evolving type system, we can now model powerful concepts like safe error handling, optional values, and composable results in a clean, type-safe way.

ts-fp-utils is a small but expressive functional programming library for TypeScript that brings together the best of Rust’s Result and Option with Go’s error semantics.

👉 NPM Package:

npm install ts-fp-utils
Enter fullscreen mode Exit fullscreen mode

💡 Why Another FP Library?

Most TypeScript codebases handle errors using plain try/catch blocks, which can get messy and unpredictable — especially in async-heavy systems or service layers.

This library was built to solve three real-world problems:

  1. Make error handling composable and predictable
  2. Avoid null checks everywhere with an expressive Option type
  3. Introduce domain-specific failures inspired by Go and DDD

In short: make failure a first-class citizen of your TypeScript code.


⚙️ The Core Modules

File Role
pair.ts Simple immutable key–value pair
option.ts Optional value wrapper (Rust’s Option)
result.ts Success/error container (Rust’s Result)
failure.ts Base error abstraction (Go’s error)
failures.ts Helper methods for working with Failure
ApiFailure.ts, EntityNotFound.ts, etc. Domain-level exception classes

🧠 Option — Safe Optional Values

Rust’s Option<T> inspired our implementation.
It lets you express “value or none” semantics clearly.

import { Option } from "ts-fp-utils";

const userId = Option.orUndefinedOrNullable(null);

console.log(userId.isEmpty()); // true

const fallback = userId.getOrElse("guest");
console.log(fallback); // guest

const name = Option.ok("John Doe")
  .map(n => n.toUpperCase())
  .get();

console.log(name); // JOHN DOE
Enter fullscreen mode Exit fullscreen mode

Async? No problem 👇

const asyncOpt = await Option.of(async () => "Hello Async");
console.log(asyncOpt.get()); // Hello Async
Enter fullscreen mode Exit fullscreen mode

🧩 Composing with Result

The Result<T, E> type (inspired by Rust) ties it all together — representing success or failure outcomes without throwing exceptions.

import { Result } from "ts-fp-utils";

function divide(a: number, b: number): Result<number, Failure> {
  if (b === 0) return Result.err(new IllegalArgument("Division by zero"));
  return Result.ok(a / b);
}

const result = divide(10, 0);
if (result.isErr()) console.error(result.unwrapErr().message);
Enter fullscreen mode Exit fullscreen mode

💥 Failure — When Things Go Wrong

Inspired by Go’s error and Kotlin’s Result.Failure, this class makes failures explicitly representable and composable.

import { Failure } from "ts-fp-utils";

try {
  const op = Failure.of(() => {
    throw new Error("Network timeout!");
  });
  op.ifPresent(f => console.error("Failure occurred:", f.message));
} catch (err) {
  console.error("Unexpected exception:", err);
}
Enter fullscreen mode Exit fullscreen mode

Wrapping causes

const wrapped = Failure.wrap("Outer context", new TypeError("Invalid response"));
console.log(wrapped.toString());
// Failure{message='Outer context', cause=TypeError('Invalid response')}
Enter fullscreen mode Exit fullscreen mode

🧰 Failure Helpers

Helper methods to make common failure patterns easier.

import {
  emptyFailure,
  failureWithMessage,
  wrapFailure,
  wrapExistingFailure
} from "ts-fp-utils";

const fail = failureWithMessage("Validation failed");
console.log(fail.isPresent()); // true

const wrapped = wrapFailure("Database insert failed", new Error("Unique constraint violated"));
console.log(wrapped.message); // Database insert failed
Enter fullscreen mode Exit fullscreen mode

🚨 Domain-Specific Failures

You can define meaningful, domain-aware failures — no more vague throw new Error().

import {
  EntityNotFound,
  EntityAlreadyExists,
  AuthFailure,
  OperationNotAllowed
} from "ts-fp-utils";

function findUser(id: string) {
  if (!id) throw new AuthFailure("User not authenticated.");
  throw new EntityNotFound(`User with ID ${id} not found.`);
}

try {
  findUser("123");
} catch (err) {
  if (err instanceof EntityNotFound) console.error("Not Found:", err.message);
}
Enter fullscreen mode Exit fullscreen mode

Built-in failure types include:

  • ApiFailure
  • AuthFailure
  • EntityAlreadyExists
  • EntityNotFound
  • EntityValidationFailed
  • IllegalArgument
  • IllegalState
  • InternalFailure
  • InvalidRequest
  • OperationNotAllowed

Each extends the base Failure class and adds clear semantic meaning to your application’s flow.


🧱 Built for Real Systems

This library was designed for use in backend applications, domain-driven systems, and API layers where:

  • You need robust error propagation
  • You prefer explicit control flow over exceptions
  • You want Go-like simplicity with Rust-like safety

🚀 Try It Out

npm install ts-fp-utils
Enter fullscreen mode Exit fullscreen mode

Then start composing safe, expressive TypeScript today.
No more surprise undefined, no more hidden exceptions.


💬 Final Thoughts

By combining Rust’s Result & Option patterns with Go’s pragmatic error philosophy, we can bring clarity and safety to everyday TypeScript.

If you’re tired of fighting try/catch, or your codebase is full of null guards — give this library a spin.

GitHub: github.com/veerakumarak/ts-fp-utils
NPM: npmjs.com/package/ts-fp-utils


🧡 Happy functional programming in TypeScript!

Top comments (0)