代数的データ型
Essential前提知識
Rustで定義するすべての型は2つの操作から構成される:組み合わせと選択だ。この2つの操作を繰り返し適用することで、任意のデータ構造を記述できる。
この考え方には名前がある:代数的データ型(algebraic data types)、略してADTだ。
2つの操作
最初の操作は**直積(product)**だ:複数の値を1つに組み合わせる。2次元空間の点はx座標 かつ y座標だ。ユーザーレコードは名前 かつ 年齢 かつ メールアドレスだ。直積を持っていれば、常にそのすべての部分を持っている。Rustではこれを struct と書く。
2番目の操作は**直和(sum)**だ:固定された選択肢の集合から1つを選ぶ。信号機は赤 または 黄 または 緑だ。パース結果は有効な値 または エラーだ。直和を持っていれば、常にちょうど1つのバリアント(variant)を持っている。Rustではこれを enum と書く。
Rustのすべてのユーザー定義型は、これら2つの操作の何らかの組み合わせだ。
なぜ「代数的」か
この用語は値の数を数えることから来ている。
直積型(product type) はちょうど 個の可能な値を持つ — の値と の値の各組み合わせに1つ対応する。bool が2つの値を持ち u8 が256個の値を持つなら、両方を保持する構造体は 個の可能な値を持つ。
直和型(sum type) はちょうど 個の可能な値を持つ — の各値に1つ、 の各値に1つだ。2つのユニットバリアントを持つ列挙型(enum)は 個の可能な値を持つ — ちょうど bool と同じだ。
この数え方の視点はデータモデルを設計するときに重要だ。型がドメインの必要より多くの可能な状態を持つ場合、コードは実行時に不可能な状態を防ぐガードが必要になる。適切な数の状態を持つ型を設計することで、コンパイラがそのガードを排除できる。
RustでADTが重要な理由
RustでのADTの恩恵は**網羅性(exhaustiveness)**だ。列挙型を match するとき、Rustはすべてのバリアントを処理することを要求する。コンパイル時にケースを忘れることはできない。
列挙型に新しいバリアントを追加すると、キャッチオール(catch-all)を持たないすべての match はコンパイルに失敗する — 組み込みのチェックリストとして機能する。ADTはドメインモデリングを正確さの強制という形式に変える。
直積と直和の組み合わせ
真の力は2つの操作を組み合わせることから来る。Result<T, E> は直和型だ:Ok(T) または Err(E)。内部の T はそれ自体が直積型 — 複数のフィールドを保持する構造体 — かもしれない。
enum Shape {
Circle { radius: f64 },
Rectangle { width: f64, height: f64 },
Triangle { base: f64, height: f64 },
}
「and(かつ)」と「or(または)」で英語で記述できるデータモデルはすべて、ADTに直接マッピングされる。
Rustのカバレッジ
Rustの struct は直積型をカバーし、enum は直和型をカバーする。合わさると、ADTで表現可能なすべての空間をカバーする。選択可能性を表現するために継承階層、判別共用体のワークアラウンド、またはnullableフィールドは必要ない — すべてはハックなしで struct と enum で処理される。