Limited Interest

Implementing a safe transmute in TypeScript

Technical

I have been trying to make an equivalent of the Rust’s versions of safe transmute in TypeScript. A transmute in Rust reinterprets the bits of one type as another type. This is obviously an unsafe thing to do, but various tricks exist to do this within safe Rust, either through a soundness hole or by using mechanisms provided by the operating system.

This came about in TypeScript after I noticed the implication of Array accesses always being of a type, which lets you write code like

const totallyNotUndefined = <T>(): T => [][0];

// can be used like
const myString: string = totallyNotUndefined();

which when called gives a value of type T that is actually undefined, without using any tricks that are known to be “unsafe” to use, like is or as.

I saw that if I could define a function that took a T and claimed to return undefined but actually returned T, it would be possible to make a safe transmute.

const totallyUndefined = <T>(v: T): undefined => {
  // filled in later
};

const safeUndefinedCheck = <T>(v: T | undefined) => v ?? [][0];

const totallySafeTransmute = <T, V>(v: T): V => {
  const a: V | undefined = totallyUndefined(v);

  return safeUndefinedCheck(a);
};

This is where it stood for months, I had versions that cheated but never one that would do it cleanly. That was until I noticed something interesting

interface A {
  t: undefined;
}

interface B<T> {
  t: T;
}

const setT = <T>(ab: A | B<T>, v: T) => {
  ab.t = v;
};

const totallyUndefined = <T>(v: T): undefined => {
  const a: A = { t: undefined };
  setT(a, v);
  return a.t;
};

If you have a type union, you don’t need to make modifications that are valid for both, they only need to be valid for one, which for mutable objects is bad.

In full, the code looks like this and typechecks absolutely fine. See this TypeScript playground to mess around with the code and run it.

interface A {
  t: undefined;
}

interface B<T> {
  t: T;
}

const setT = <T>(ab: A | B<T>, v: T) => {
  ab.t = v;
};

const totallyUndefined = <T>(v: T): undefined => {
  const a: A = { t: undefined };
  setT(a, v);
  return a.t;
};

const safeUndefinedCheck = <T>(v: T | undefined) => v ?? [][0];

const totallySafeTransmute = <T, V>(v: T): V => {
  const a: V | undefined = totallyUndefined(v);

  return safeUndefinedCheck(a);
};

const myNumber: number = totallySafeTransmute("Hello, World!");

console.log(myNumber);