Skip to content

ImmutableMutable

ghaerdi edited this page Oct 4, 2026 · 1 revision

Immutable<T> & Mutable<T>

Immutable<T> and Mutable<T> are type-only helpers (no runtime export) that flip the readonly modifiers of a type, recursively. They are exported from the package root and from the ./immutable subpath.

Immutable<T>

Recursively makes every property readonly and converts mutable collections to their readonly counterparts:

Source Becomes
Array<T> ReadonlyArray<Immutable<T>>
Map<K, V> ReadonlyMap<Immutable<K>, Immutable<V>>
Set<T> ReadonlySet<Immutable<T>>
plain object { readonly [K in keyof T]: Immutable<T[K]> }
anything else itself (see Pass-through types)
import type { Immutable } from "@ghaerdi/rustify";

type User = {
  name: string;
  tags: string[];
  metadata: { role: string };
};

type FrozenUser = Immutable<User>;
// {
//   readonly name: string;
//   readonly tags: readonly string[];
//   readonly metadata: { readonly role: string };
// }

const user: Immutable<User> = {
  name: "Alice",
  tags: ["admin"],
  metadata: { role: "admin" },
};

// Compile errors: user.name = "Bob" and user.tags.push("x") are both rejected.

Mutable<T>

The deep inverse: recursively removes readonly modifiers and converts readonly collections back to their mutable counterparts (ReadonlyArray → Array, ReadonlyMap → Map, ReadonlySet → Set). Use it as an escape hatch when mutation is intentional — for example inside a builder that needs to fill in a frozen input.

import type { Immutable, Mutable } from "@ghaerdi/rustify";

type Frozen = Immutable<{ readonly name: string; readonly tags: readonly string[] }>;
type MutableUser = Mutable<Frozen>;
// { name: string; tags: string[] }

const user: MutableUser = { name: "Alice", tags: ["admin"] };
user.tags.push("editor"); // allowed — Mutable removed the readonly

Mutable<Frozen> restores the original shape, so Mutable<Immutable<T>> is assignable both ways with T.

Mutable<T> changes the type, not the value. If you already have a deeply frozen instance, converting the type does not make it mutable at runtime.

Pass-through types

Some types cannot be made immutable through readonly alone — readonly on a property of a Date, a RegExp, a Promise, a WeakMap or a WeakSet still allows the object's own mutating methods (setTime, exec, …). These, plus functions, ReadonlyMap/ReadonlySet/ReadonlyArray (for Immutable) and primitives, pass through unchanged.

import type { Immutable } from "@ghaerdi/rustify";

type D = Immutable<Date>; // still Date — getTime(), setTime() still available
type F = Immutable<(x: number) => string>; // still the same function type
type S = Immutable<string>; // still string

Usage

  • Annotate a public API's return type with Immutable<T> so callers cannot mutate your internal structures.
  • Keep Mutable<T> internal (e.g. in a builder) so the immutability guarantee holds everywhere else.
  • Immutable<T> is idempotent: applying it twice changes nothing.

See Also

Clone this wiki locally