Why is Readonly<T> assignable to T, but ReadonlyArray<T> is not assignable to Array<T>?

Viewed 400
type Foo = {a: number}
let obj: Foo = {} as Readonly<Foo>
let arr: number[] = [] as ReadonlyArray<number>

I understand that the ReadonlyArray type removes any mutative Array methods, and that means the readonly array is not adhering to the expected interface of an array.

But I don't understand why readonly objects are considered safe to pass to a function that expects a mutable object. Technically, the readonly object is missing an implicit setter method, so it's not really adhering to the expected mutable interface.

Playground link

1 Answers

ReadonlyArray is, in fact, another type, so Array and ReadonlyArray are different structurally. As you noticed, instances of ReadonlyArray are missing some methods like push and pop.

It doesn't work like that with objects. There is no separate ReadonlyObject nor ReadonlyObjectConstructor. Many operations that should not be allowed are allowed:

type Foo = {a: number}

let mutable: Foo = { a: 0 };
let immutable: Readonly<Foo> = { a: 0 };

/**
 * Both work without error.
 */
mutable = immutable;
immutable = mutable;

/**
 * These two work as well.
 */
Object.assign(immutable, { foo: 1 });
Object.defineProperty(immutable, 'bar', {
    value: 2
});

TypeScript uses a structural type system. Two objects of exactly the same shape will be assignable to each other. However, the readonly modifier doesn't affect the structure, only behavior.

Related