---
id: wiki-2026-0508-union-types
title: Union Types
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [Sum Types, Tagged Union, Discriminated Union, Variant]
duplicate_of: none
source_trust_level: A
confidence_score: 0.9
verification_status: applied
tags: [type-system, union, sum-type, discriminated-union, typescript, rust]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
  language: TypeScript / Rust / Haskell
  framework: language-agnostic
---

# Union Types

## 매 한 줄
> **"매 값이 A 또는 B (또는 C, ...) 의 하나 — 매 sum type 의 set-theoretic 표현"**. Union types 은 1970s ML 의 datatype, 1980s C union (untagged) 부터, 2026 TypeScript discriminated union, Rust enum, Haskell ADT, Python `X | Y` (PEP 604) 까지 매 type-safe variant modeling 의 표준. 매 product type (struct/tuple) 의 dual: AND 가 product, OR 가 sum.

## 매 핵심

### 매 두 종류
- **Untagged union (C-style)**: 매 같은 메모리 영역, runtime 에 active variant 알 수 없음 — unsafe.
- **Tagged / discriminated union**: 매 tag field 로 variant 구분 — type-safe (Rust `enum`, TS discriminated, Haskell ADT).

### 매 set theory 관점
- 매 `A | B` ≅ A ∪ B (set union, 중복 제거).
- 매 `{a:1} | {b:2}` ≅ `{a:1, b?:undefined} ∪ {a?:undefined, b:2}` (TS structural).
- 매 cardinality: `|A | B| = |A| + |B|` (disjoint).

### 매 응용
1. API response — `Success<T> | Error` (Result type).
2. State machine — `Idle | Loading | Success | Error`.
3. Parser AST — `BinOp | UnaryOp | Literal | Variable`.

## 💻 패턴

### TypeScript: discriminated union
```typescript
type Shape =
  | { kind: "circle"; radius: number }
  | { kind: "square"; side: number }
  | { kind: "rect"; width: number; height: number };

function area(s: Shape): number {
  switch (s.kind) {
    case "circle": return Math.PI * s.radius ** 2;
    case "square": return s.side ** 2;
    case "rect":   return s.width * s.height;
  }
}
```

### TypeScript: exhaustiveness check
```typescript
function area(s: Shape): number {
  switch (s.kind) {
    case "circle": return Math.PI * s.radius ** 2;
    case "square": return s.side ** 2;
    case "rect":   return s.width * s.height;
    default: {
      const _exhaustive: never = s;  // 매 새 variant 추가 시 compile error
      throw new Error(`unhandled: ${_exhaustive}`);
    }
  }
}
```

### TypeScript: Result type
```typescript
type Result<T, E = Error> =
  | { ok: true; value: T }
  | { ok: false; error: E };

async function fetchUser(id: string): Promise<Result<User>> {
  try { return { ok: true, value: await api.get(id) }; }
  catch (e) { return { ok: false, error: e as Error }; }
}

const r = await fetchUser("1");
if (r.ok) console.log(r.value.name);
else console.error(r.error.message);
```

### Rust: enum with data
```rust
enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(u8, u8, u8),
}

fn handle(msg: Message) {
    match msg {
        Message::Quit => println!("quit"),
        Message::Move { x, y } => println!("move to {},{}", x, y),
        Message::Write(s) => println!("write {}", s),
        Message::ChangeColor(r, g, b) => println!("rgb({},{},{})", r, g, b),
    }
}
```

### Rust: Option / Result (built-in unions)
```rust
fn divide(a: f64, b: f64) -> Result<f64, String> {
    if b == 0.0 { Err("division by zero".into()) }
    else { Ok(a / b) }
}

match divide(10.0, 2.0) {
    Ok(v) => println!("{}", v),
    Err(e) => eprintln!("error: {}", e),
}
```

### Haskell: ADT (sum + product)
```haskell
data Tree a = Leaf | Node (Tree a) a (Tree a)

depth :: Tree a -> Int
depth Leaf = 0
depth (Node l _ r) = 1 + max (depth l) (depth r)

-- 매 Maybe = Nothing | Just a — sum type
safeDiv :: Int -> Int -> Maybe Int
safeDiv _ 0 = Nothing
safeDiv a b = Just (a `div` b)
```

### Python 3.10+: union via `|`
```python
from dataclasses import dataclass

@dataclass
class Circle: radius: float
@dataclass
class Square: side: float

Shape = Circle | Square

def area(s: Shape) -> float:
    match s:
        case Circle(radius=r): return 3.14159 * r * r
        case Square(side=x):   return x * x
```

### TypeScript: narrowing without discriminant
```typescript
type Pet = { swim: () => void } | { fly: () => void };
function move(p: Pet) {
  if ("swim" in p) p.swim();  // 매 narrowed
  else p.fly();
}
```

## 매 결정 기준

| 상황 | Approach |
|---|---|
| 2-3 variants, simple | TS plain union `A \| B` |
| Many variants, type-safe dispatch | discriminated union with `kind` |
| Result / Option | TS Result type / Rust built-in / Haskell Maybe |
| Cross-language ADT | Rust enum / Haskell data |
| Untagged (FFI / memory layout) | C union / Rust `union` (unsafe) |
| Exhaustive matching | switch + `never` (TS) / `match` (Rust/Python) |

**기본값**: 매 discriminated union with literal `kind`/`tag` field — exhaustiveness 보장.

## 🔗 Graph
- 부모: [[Type System]] · [[Algebraic Data Types]]
- 변형: [[Discriminated Union]] · [[Tagged Union]] · [[Sum Types]] · [[Variant]]
- 응용: [[Result Type]] · [[Option Type]] · [[State Machine]] · [[AST]]
- Adjacent: [[Pattern Matching]] · [[Type Narrowing]] · [[Exhaustiveness Check]]

## 🤖 LLM 활용
**언제**: 매 모델링 "A or B" 의 alternatives, error handling (Result), state representation.
**언제 X**: 매 단순 enum (no associated data) — plain enum 이 나음.

## ❌ 안티패턴
- **Boolean flags 의 union 흉내**: `{ ok: boolean, value?, error? }` — 매 `value && error` 동시 가능 → bug. Discriminated union 으로.
- **Discriminant 없는 큰 union**: 매 narrowing 불가능 — `kind`/`tag` 추가.
- **`default` 없는 switch**: 매 새 variant 추가 시 silent miss — `never` exhaustive check.
- **Rust untagged `union`**: 매 unsafe 만 사용 — 99% 의 case 는 `enum`.

## 🧪 검증 / 중복
- Verified (TypeScript handbook, Rust book, Haskell report 2010).
- 신뢰도 A.

## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — union types full content (4 langs, exhaustive, Result, 8 patterns) |
