ross@normalcool
2026-01-03

TypeScript Result Proto

I get why error catches in TS are unknown but i sure don’t like working with it. Also find try{}catch(e){} to just be annoying in general. Not uncommon seeing multiple places within a try that could fail. I like making function results more certain

interface ResultOk<T> {
  kind: "ok";
  value: T;

  isOk(): this is ResultOk<T>;
  isErr(): this is ResultErr;

  match<U>(handlers: {
    ok: (value: T) => U;
    err: (err: ResultErr["err"]) => U;
  }): U;

  unwrap(): T;
  unwrapErr(): never;
}

const okProto = {
  isOk(): this is ResultOk<any> {
    return true;
  },
  isErr(): this is ResultErr {
    return false;
  },
  match<U>(this: ResultOk<any>, handlers: any): U {
    return handlers.ok(this.value);
  },
  unwrap(this: ResultOk<any>) {
    return this.value;
  },
  unwrapErr(): never {
    throw new Error("Called unwrapErr on Ok");
  },
};

const ok = <T>(value: T): ResultOk<T> => {
  const o = Object.create(okProto);
  o.value = value;
  o.kind = "ok";
  return o;
};
interface ResultErr {
  kind: "err";
  err: {
    tag: ErrTags;
    msg: string;
    metadata?: Record<string, any>;
  };

  isOk(): this is ResultOk<never>;
  isErr(): this is ResultErr;

  match<U>(handlers: {
    ok: (value: never) => U;
    err: (err: ResultErr["err"]) => U;
  }): U;

  unwrap(): never;
  unwrapErr(): ResultErr["err"];
}

const errProto = {
  isOk(): this is ResultOk<never> {
    return false;
  },
  isErr(): this is ResultErr {
    return true;
  },
  match<U>(this: ResultErr, handlers: any): U {
    return handlers.err(this.err);
  },
  unwrap(): never {
    throw new Error("Called unwrap on Err");
  },
  unwrapErr(this: ResultErr) {
    return this.err;
  },
};

const err = <T>(err: Err): ResultErr => {
  const o = Object.create(errProto);
  o.err = err;
  o.kind = "err";
  return o;
};