閉じる

タグ付き共用体(Tagged Union)とは?使い方とメリットをわかりやすく解説

タグ付き共用体(Tagged Union)は、ひとつの値が複数の「形」のうちどれか1つを確実に表すためのデータ構造です。

各「形」には判別用のタグが付き、どの形かを安全に見分けられます。

これにより条件分岐のミスを防ぎ、読みやすいコードを書きやすくなります。

初心者の方でも、使い方のパターンを覚えればすぐに活用できます。

エンジニアの求人
読み込み中…

タグ付き共用体(Tagged Union)とは

初心者向けの基本

用語の整理

タグ付き共用体は「値の種類ごとに名前(タグ)が付いた入れ物」で、取りうる形を明示的に列挙する考え方です。

それぞれの形をバリアント(variant)と呼び、例えば「現金」か「カード」のように互いに排他的な状態を1つだけ持ちます。

簡単なイメージ

例えば支払い方法を表すとき、ただの文字列だと”cash”や”card”の綴りを間違えるかもしれません。

タグ付き共用体ならCashCardという決まった選択肢だけを許し、さらにCardには「下4桁」などの付随データも一緒に持てます。

「どの種類の値か」と「その値に必要な追加情報」を1セットで扱えることがポイントです。

合併型(Union)との違い

違いの概要

合併型(Union)は「AかBかC…のどれか」という幅の広い型を指します。

タグ付き共用体はその一種ですが、各ケースに必ず「タグ(判別子)」が付いている点が特徴です。

これにより、分岐時に「今はどのケースか」を安全かつ簡単に判定でき、取りこぼしやタイプミスを防げます。

以下は違いの整理です。

項目単純な合併型(Union)タグ付き共用体(Tagged Union)
自己説明性どの形か分かりにくいタグで一目で分かる
実行時の安全性誤った型扱いが起きやすい不正な扱いを型で防げる
コンパイル時チェック限定的網羅性チェックが効きやすい
分岐の書きやすさ条件が複雑になりやすいタグでシンプルに分岐
IDEの補完ばらつきがあるケースごとの補完が強力

結論として、タグ付き共用体は「安全に分岐できる合併型」です。

使える言語例

  • Rust(enum): タグ付き共用体を言語機能として提供します。
  • TypeScript(判別可能なユニオン): オブジェクトのプロパティをタグにして使います。
  • Swift(enum with associated values): 値付きケースを自然に表現できます。
  • Kotlin(sealed class/enum class): 継承と組み合わせて安全な分岐が可能です。
  • F#/OCaml/Haskell(代数的データ型): もともとこの考え方が基本にあります。
  • C/C++: unionと手動タグ(構造体のフィールド)を組み合わせて実現します。

タグ付き共用体のメリット

型安全でミスを防ぐ

存在しない値や不完全な状態を、コンパイル時に防げます。

例えば「カード」なのにカード情報が空、という状態を型が許さないように設計できます。

結果として、実行時のバグを減らしやすくなります。

パターンマッチで読みやすい

タグがあるので、ケースごとに分岐するコード(パターンマッチ)が簡潔で読みやすくなります。

分岐条件が明確になるため、レビューや保守のコストも下がります。

nullを避ける設計

nullを使わずに「ある/ない」を表現するOption型のようなパターンが自然に書けます。

「ない」も1つのバリアントとして明示することで、見落としを防げます。

エラー処理に強い

成功と失敗を1つの型で表すResult型のパターンにより、例外に頼らず安全にエラーを扱えます

戻り値の型だけで「成功か失敗か」と「必要な情報」が分かります。

データ構造をシンプルに保つ

複数のフラグやnullチェックが散らばる設計を避け、「今はこの状態」という1点に絞ったデータ構造にできます。

状態遷移も追いやすくなり、ロジックが見通しやすくなります。

プログラミングでの使い方

バリアントを定義する

Rustならenum

各バリアントを列挙して定義します。

値を持つバリアントも簡単です。

TypeScriptなら判別子プロパティ

オブジェクトにkind(など)のプロパティを付けてタグにします。

タグで分岐する

パターンマッチ

Rustのmatch、TypeScriptのswitchなどでタグを見て分岐します。

タグがあることで条件が単純化します。

データを持つバリアント

付随データを一緒に管理

「カード」ならカード下4桁、「エラー」ならエラーメッセージのように、必要な情報をそのケースにだけ結びつけられます

全ケースを網羅する

コンパイラに見てもらう

Rustでは未網羅のmatchに警告やエラーが出ます。

TypeScriptでもnever型を使って「すべて扱っているか」を確認できます。

デフォルト分岐を減らす

defaultを安易に使わない

default(または_のような総受け)は便利ですが、新しいバリアントを見落とす原因になります。

基本は各ケースを明示し、どうしても必要なときだけdefaultを使います。

コード例で学ぶ

Rustの例

支払い方法の分岐

Rust
enum Payment {
    Cash,
    Card { last4: String },
}

fn describe(p: Payment) -> String {
    match p {
        Payment::Cash => "現金で支払います".to_string(),
        Payment::Card { last4 } => format!("カード(下4桁 {})で支払います", last4),
    }
}

fn main() {
    let a = Payment::Cash;
    let b = Payment::Card { last4: "1234".into() };
    println!("{}", describe(a));
    println!("{}", describe(b));
}

バリアントごとに必要な情報だけを持てるので、不正な組み合わせが入り込みにくくなります。

TypeScriptの例

判別可能なユニオン

JavaScript
type Payment =
  | { kind: "Cash" }
  | { kind: "Card"; last4: string };

function describe(p: Payment): string {
  switch (p.kind) {
    case "Cash":
      return "現金で支払います";
    case "Card":
      return `カード(下4桁 ${p.last4})で支払います`;
    // defaultは書かずに、全ケースを明示するのがおすすめ
  }
}

kindがタグの役割を果たします。

新しいバリアントを追加すると、未対応の分岐に気づけます

UI状態の管理

状態をタグ付き共用体で表す

読み込み中/成功/失敗/空などのUI状態は、タグ付き共用体と相性が良いです。

JavaScript
type LoadState<T> =
  | { kind: "Loading" }
  | { kind: "Loaded"; data: T }
  | { kind: "Error"; message: string }
  | { kind: "Empty" };

function renderUser(state: LoadState<{ name: string }>): string {
  switch (state.kind) {
    case "Loading":
      return "読み込み中…";
    case "Loaded":
      return `ようこそ、${state.data.name}さん`;
    case "Error":
      return `エラー: ${state.message}`;
    case "Empty":
      return "データがありません";
  }
}

状態が1つに限定されるため、if文とフラグの組み合わせよりも明快です。

エラー処理の実装

RustのResult型で安全に扱う

Rust
fn parse_age(s: &str) -> Result<u32, String> {
    match s.parse::<u32>() {
        Ok(n) if n <= 150 => Ok(n),
        Ok(_) => Err("年齢が不正です".to_string()),
        Err(_) => Err("数値に変換できません".to_string()),
    }
}

fn main() {
    match parse_age("42") {
        Ok(age) => println!("年齢: {}", age),
        Err(msg) => eprintln!("エラー: {}", msg),
    }
}

成功(Ok)と失敗(Err)を型で表すことで、例外に頼らない明確なフローになります。

TypeScriptで簡易Resultを作る

JavaScript
type Result<T, E> =
  | { kind: "Ok"; value: T }
  | { kind: "Err"; error: E };

function parseAge(s: string): Result<number, string> {
  const n = Number(s);
  if (!Number.isFinite(n)) return { kind: "Err", error: "数値に変換できません" };
  if (n < 0 || n > 150) return { kind: "Err", error: "年齢が不正です" };
  return { kind: "Ok", value: Math.floor(n) };
}

const r = parseAge("42");
switch (r.kind) {
  case "Ok":
    console.log("年齢:", r.value);
    break;
  case "Err":
    console.error("エラー:", r.error);
    break;
}

エラー情報も成功結果も、どちらも型安全に受け取れるため、呼び出し側での取り扱いが明確になります。

まとめ

タグ付き共用体(Tagged Union)は、複数の形を取るデータを「タグ付き」で安全に表す仕組みです。

タグにより分岐が分かりやすくなり、網羅性チェックでミスを防げます。

nullに依らない設計やエラー処理にも強く、UI状態などの表現にも向いています。

初心者の方は、まずは小さなドメイン(支払い方法、UIの読み込み状態、成功/失敗の結果など)から試し、「各ケースをタグで明示し、全てのケースを必ず扱う」という基本を身につけていくと、コードの読みやすさと安全性が大きく向上します。

エンジニアの求人
読み込み中…
プログラミング - データ表現と数値

この記事を書いた人
エーテリア編集部
エーテリア編集部

このサイトでは、プログラミングをこれから学びたい初心者の方に向けて記事を書いています。 基本的な用語や環境構築の手順から、実際に手を動かして学べるサンプルコードまで、わかりやすく整理することを心がけています。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!