본문 바로가기
C.W.K.
Stream
Lesson 03 of 05 · published

any, unknown, never: 타입 시스템의 세 경계

~12 min · primitives, any, unknown, never, top-type, bottom-type

Level 0Curious
0 XP0/93 lessons0/23 achievements
0/100 XP to next level100 XP to go0% complete
"any는 검사를 끄고, unknown은 검사를 미루고, never는 가능한 값이 없다고 선언해."

any: 타입 시스템에서 빠져나가는 문

any에는 무엇이든 넣을 수 있고, 그 값에 무엇이든 할 수 있어. 존재하지 않는 속성을 읽고 함수처럼 호출해도 컴파일러가 막지 않아. 한 번 any가 계산에 섞이면 결과도 any로 퍼지기 쉬워서 주변 타입의 보장까지 조용히 약해진다.

낡은 JavaScript 라이브러리나 점진적 이전 과정에서 잠깐 필요할 수는 있어. 그럴 때도 범위를 좁히고 왜 필요한지 보이게 해. any는 ‘어떤 타입이든’이라는 넓은 타입이 아니라 ‘여기서는 타입 검사를 하지 않겠다’는 선택이야.

unknown: 믿기 전의 값

unknown도 무엇이든 받을 수 있지만 곧바로 사용할 수는 없어. 문자열 메서드를 부르려면 typeof x === 'string'으로 확인하고, 객체 속성을 읽으려면 모양을 검증해야 해. 외부 JSON, 사용자 입력, catch 변수처럼 아직 신뢰할 근거가 없는 값에 알맞아.

unknown은 불편함을 만드는 타입이 아니라 검증해야 할 경계를 표시하는 타입이야. 한 번 안전하게 좁히면 안쪽에서는 정확한 타입으로 계속 흐른다. 입력 경계에서 unknown을 쓰고 검증 뒤 도메인 타입으로 바꾸는 구조가 가장 읽기 좋아.

never: 가능한 값이 하나도 없는 자리

never는 어떤 값도 들어올 수 없는 타입이야. 항상 예외를 던지는 함수, 끝없이 도는 함수의 반환 타입에서 나타날 수 있어. 더 실용적인 자리는 유니언의 모든 경우를 처리한 뒤야. 남은 값이 정말 없다면 변수는 never로 좁혀져.

그래서 assertNever(x: never) 같은 함수가 완전성 검사에 쓰여. 새 변형을 유니언에 추가하고 switch 처리를 빼먹으면 x가 더는 never가 아니므로 컴파일 오류가 생겨. 컴파일러가 빠진 경우의 작업 목록을 만들어 주는 셈이지.

셋을 한 줄로 비교해

any는 값의 타입을 알고 싶지 않을 때, unknown은 아직 알지 못해 확인이 필요할 때, never는 값이 존재할 수 없을 때 써. 특히 any와 unknown을 ‘둘 다 아무거나’로 묶지 마. 하나는 안전장치를 끄고 다른 하나는 안전장치가 작동하도록 멈춰 세워.

불확실하면 unknown, 불가능하면 never, any는 근거가 있을 때만. 이 순서만 지켜도 타입 시스템에 생기는 구멍 대부분을 막을 수 있어.

피파의 고백

예전에는 복잡한 오류를 만나면 any로 덮고 다음 일로 갔어. 빠르긴 했지. 그런데 그 값이 세 함수 뒤에서 터지면 원인은 사라지고 증상만 남더라. unknown으로 멈춰 세우면 바로 그 경계에서 검증하게 돼. 몇 줄 더 쓰고 몇 시간을 돌려받는 거래야.

Code

any 는 다 통과시켜. unknown 은 narrow 강제.·typescript
// any — off switch (이거 손 안 뻗어).
let danger: any = 'hello';
danger = 42;                  // ✅ any 가 뭐든 받음
danger = { foo: { bar: 1 } };
danger.foo.bar.baz.qux();     // ✅ 컴파일 — 그리고 runtime 에 크래시
                              // Loose 함이 모든 접근 통해 퍼짐.

// unknown — 조심 switch.
let careful: unknown = 'hello';
careful = 42;                 // ✅ assignment OK
careful = { foo: 1 };         // ✅ assignment OK

careful.toUpperCase();        // ❌ Object is of type 'unknown'
careful.foo;                  // ❌ Object is of type 'unknown'

// 먼저 narrow 해야 함:
if (typeof careful === 'string') {
  careful.toUpperCase();      // ✅ string 으로 narrow
}
Bottom 타입으로서의 never — 3개의 canonical 사용·typescript
// never — 불가능.

// 1. 정상으로 return 안 하는 함수의 return type.
function throwIt(msg: string): never {
  throw new Error(msg);
}
function loopForever(): never {
  while (true) { /* ... */ }
}

// 2. Exhaustive switch 의 else 분기.
type Shape = { kind: 'circle'; r: number } | { kind: 'square'; s: number };

function area(shape: Shape): number {
  switch (shape.kind) {
    case 'circle': return Math.PI * shape.r ** 2;
    case 'square': return shape.s ** 2;
    default:
      // Shape 에 새 variant 추가하고 case 잊으면,
      // 이 assignment 가 compile 시점에 실패:
      const _exhaustive: never = shape;
      return _exhaustive;
  }
}

// 3. 위계 in action.
const neverVal!: never = undefined as never;
const asString: string = neverVal;  // ✅ never 는 어디든 assignable
const asNumber: number = neverVal;  // ✅ 같음
// 아무것도 never 안으로 assignable 안 됨 — 그래서 bottom.

External links

Exercise

JSON.parse 결과를 unknown으로 감싸 반환하는 parseJson(input: string): unknown을 작성해. 이어서 parseJson을 호출하고 필수 필드를 좁혀 User | null을 반환하는 parseUser도 만들어. unknownany라면 건너뛰었을 검증을 어떻게 요구하는지 확인해.
Hint
먼저 객체인지와 null이 아닌지 확인해. 그다음 name 키가 있고 값이 문자열인지 검사해야 User를 반환할 수 있어.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고
💛 by 똘이warm

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.