共用型 / Union 型

「複数の型のいずれかである」ことを表す型。実装形態によって安全性と表現力が大きく異なる。

3 つの実装形態

C union(untagged)     → 同一メモリを複数の型で解釈。タグなし。unsafe
    ↓ 安全化
Tagged union           → タグ(判別子)で現在の型を識別。パターンマッチで安全にアクセス
    ↓ 構造的
TypeScript union       → 構造的部分型。ランタイムタグ不要。型の合成

Untagged Union(C union)

union Value {
    int i;
    float f;
    char *s;
};

union Value v;
v.i = 42;
printf("%f\n", v.f);  // 未定義動作!int として書いたメモリを float として読む

C の tagged union パターン(手動)

typedef enum { INT, FLOAT, STRING } ValueTag;

typedef struct {
    ValueTag tag;
    union {
        int i;
        float f;
        char *s;
    } data;
} TaggedValue;

void print_value(TaggedValue v) {
    switch (v.tag) {
        case INT:    printf("%d\n", v.data.i); break;
        case FLOAT:  printf("%f\n", v.data.f); break;
        case STRING: printf("%s\n", v.data.s); break;
    }
}

C では tagged union を struct + enum + union で手動で構成する。タグとデータの整合性はプログラマの責任。Rust enum はこのパターンをコンパイラが保証する形で言語に組み込んだもの。

Tagged Union(Rust enum / Haskell data / Almide type)

enum Value {
    Int(i64),
    Float(f64),
    Str(String),
}

fn print_value(v: &Value) {
    match v {
        Value::Int(i)   => println!("{}", i),
        Value::Float(f) => println!("{}", f),
        Value::Str(s)   => println!("{}", s),
    }
}

→ 詳細は 列挙型 を参照

TypeScript Union(構造的 Union)

type Shape =
    | { kind: "circle"; radius: number }
    | { kind: "rect"; width: number; height: number }
    | { kind: "point" };

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

// プリミティブの union も可能
type StringOrNumber = string | number;

TypeScript Union の特異性

TypeScript の union は他の言語の union とは本質的に異なる:

// 1. 型の合成: 任意の型を | で合成できる
type Result = Success | Failure | Loading;

// 2. 型の絞り込み (narrowing): 制御フローで型が自動的に絞られる
function process(x: string | number) {
    if (typeof x === "string") {
        x.toUpperCase();  // ここでは x: string
    } else {
        x.toFixed(2);     // ここでは x: number
    }
}

// 3. ユーティリティ型との組み合わせ
type NonNullable<T> = T extends null | undefined ? never : T;

3 形態の比較

C unionTagged union (Rust等)TS union
タグなしコンパイラ管理構造的判別
メモリ最大メンバのサイズタグ + 最大バリアントのサイズJS オブジェクト
安全性unsafe(未定義動作)safe(コンパイル時保証)safe(型チェッカー保証)
網羅性チェックなしmatch で強制never 型で可能
バリアント別データ不可(全メンバ同一メモリ)可(バリアントごとに異なる型)可(構造的に異なるオブジェクト)
型の開閉—閉じた型(宣言時に確定)開いた型(後から合成可能)
主な用途メモリ節約、低レベル操作ドメインモデル、エラー処理API 型定義、柔軟なモデリング

メモリレイアウト

C union Value { int i; float f; char *s; }
┌─────────────────────┐
│  i / f / s (共有)    │  ← 最大メンバのサイズ(ここでは 8 バイト: char*)
└─────────────────────┘

Rust enum Value { Int(i64), Float(f64), Str(String) }
┌──────┬──────────────────────────┐
│ tag  │  Int / Float / Str       │  ← タグ + 最大バリアントのサイズ
│ (1B) │  (24B: String が最大)     │
└──────┴──────────────────────────┘

Rust は niche optimization により、Option<&T> のような場合にタグのオーバーヘッドをゼロにする(null ポインタをタグとして再利用)。

他の言語での Union

言語キーワード / 構文分類
CunionUntagged
C++union / std::variantUntagged / Tagged
RustenumTagged
HaskelldataTagged
Almidetype ... | ...Tagged
Swiftenum (Associated Value)Tagged
TypeScriptA | B構造的
PythonUnion[A, B] / A | B (3.10+)型ヒント(ランタイムは動的)
Scalasealed trait + case classTagged
OCamltype t = A | B of intTagged
Zigunion(enum)Tagged(明示的にタグ付き union を宣言)
Goなし(interface で模倣)—

押さえどころ(カード化候補)

関連

Graph