"Compile-time 불변성. Runtime 은 모르고, compiler 가 까먹게 두지 않아."
`readonly` 가 뭐 함
readonly 는 생성 후 write 막는 property modifier. interface Box { readonly width: number } 는 `width` 가 read-only — object 존재 후 assign 하면 compile 에러. Property 정상 read 가능; reassignment 실패.
이건 compile-time contract. Runtime 에선 property 가 일반 property, 완전히 writable. Type system 이 규칙 강제; JavaScript runtime 은 존재 자체 모름. Foundations 트랙의 2-layer 정신 모델과 일관: 타입은 erase 돼.
`readonly` 가 가는 곳
- Object property:
{ readonly id: number; name: string }— `id` 잠겨, `name` mutable. - Interface property:
interface User { readonly id: number; name: string }— 같은 규칙, 이름 붙은 선언. - Class 필드:
class Box { readonly width: number; constructor(w: number) { this.width = w } }— constructor 만 write 가능, 다른 어느 것도 안 됨. - Array 타입:
readonly number[]또는ReadonlyArray<number>— push/pop/sort/등 이 타입에서 제거됨. - Tuple 타입:
readonly [number, string]— 같은 아이디어, 위치-aware.
왜 compile-time-only 로 충분
실용적 답: 대부분 mutation 버그가 네가 제어하는 코드에서 발생. 팀이 `readonly` property 에 안 쓰기로 합의했으면, compiler 가 그 합의 강제 가능. Runtime 이 강제할 필요는 malicious 코드나 진짜 외부 mutation 에 대해서만 — 그런 case 는 frontend 앱 코드에 드물어.
더 깊은 답: `readonly` 를 runtime-enforced 로 만들려면 object freeze (느림, 깊음) 거나 proxy 로 wrap (느림, 침습적) 이 필요해. TypeScript 설계자가 거래 선택: runtime 비용 0, 실제 compile-time 안전. 진짜 runtime 불변성 필요하면 Object.freeze() 또는 Immer 같은 library 손 뻗어.
const config = { port: 5173 } as const 가 모든 property readonly 만들고 모든 값을 literal 타입으로 narrow. "이거 못 바뀌어, compiler 가 거부해야 함" 원할 때 팀이 손 뻗는 조합.readonly 가 default 로 shallow
readonly { nested: { name: string } } 는 outer 참조가 readonly 의미, 근데 nested.name 은 여전히 writable. Compiler 가 recurse 안 함. Deep readonly 원하면 mapped 타입 (Type Manipulation 트랙에서 다룸) 으로 만들거나, 표준 Readonly<T> utility — 이것도 shallow — 써. Deep 불변성은 opt-in 하는 재귀 작업.