Rustにおける型とは何か
Essential前提知識
型は約束だ。let x: u32 = 5; と書くとき、あなたはコンパイラに「x は32ビット符号なし整数を保持する」と伝えている — そしてコンパイラは x が使われるあらゆる場所でその約束を厳守させる。
これは単純に聞こえるが、型は複数の仕事を同時にこなしている。
型がコンパイラに伝えること
すべての型は3つのことを伝える:
- サイズ(size):その型の値がメモリ上で何バイトを占めるか。
- レイアウト(layout):それらのバイトがどう配置されているか(どのバイトが整数を符号化し、どれがパディングで、どれが判別子か)。
- 操作(operations):どのメソッド、演算子、トレイト実装が利用可能か。
コンパイラがすべての値の型を知っているため、ランタイムの型タグなしで正しいマシンコードを生成できる。型情報はコンパイル時に存在し、その後消去される — 実行中のプログラムは「これは何の型か?」と問うことはない。
静的型付け
Rustは**静的型付け(statically typed)**言語だ:すべての式はコンパイル時に完全に判明している単一の型を持つ。any 型はなく、バイトを黙って再解釈する暗黙の型変換もなく、明示的に unsafe コードを書かない限り型システムを回避する方法もない。
型を明示的に書く必要はほとんどない — Rustの型推論がほとんどの場合で補ってくれる。しかし型は常に存在する。推論で判断できない場合、コンパイラはアノテーションを求める。
let x = 5; // i32 と推論される
let y: u8 = 5; // アノテーション付き
let z = 5u8; // リテラルへの型サフィックス
プリミティブ型とユーザー定義型
Rustの型システムには2つの層がある。
**プリミティブ型(primitive types)**は言語に組み込まれている:整数族と浮動小数点族、bool、char、そしてユニット型 ()。そのサイズと操作は言語仕様で固定されている。
ユーザー定義型(user-defined types) — struct、enum、そしてそれらを組み合わせた型 — はそれ以外のすべてだ。コンパイラはこれらを同じ精度で扱う:u32 のサイズ、レイアウト、利用可能な操作を知るのと同様に、ユーザー定義型についても知っている。
この対称性は意図的だ。自分の型を定義することは、組み込み型を使うことと同等に第一級(first-class)扱いだ。Point 構造体(struct)、Color 列挙型(enum)、標準ライブラリの Vec<u8> — すべて同じ型システムの中に、対等な立場で存在する。
RustとZigの型の違い
Zigも静的型付けでネイティブコードにコンパイルされる。しかしZigはジェネリックパラメータに anytype を持ち、@bitCast によるバイトレベルの再解釈は日常的な操作だ。Zigは「ここでの本当の型は私が知っている、信じてくれ」と言う手段を提供している。
Rustはその取引を難しくしている。as キャストは定義された数値変換をカバーするが、無関係な型間でバイトを再解釈することはしない。生バイトの再解釈には unsafe と std::mem::transmute が必要だ — あなたが全責任を負うという明示的で可視的な宣言だ。一般的なやり方は型システムの中に留まり、コンパイラにミスを検出させることだ。
実践的な意味
Rustの関数シグネチャを読むと情報が得られる:
fn area(width: f64, height: f64) -> f64 { width * height }
入力が64ビット浮動小数点数であること、出力も64ビット浮動小数点数であること、他の型は関与しないことがわかる。コンパイラはすべての呼び出し箇所でこれを検証する。u32 を黙って渡して暗黙変換された結果を得ることはできない。
この厳格さは意図的だ。型システムはRustの第一の防衛線だ — 所有権より前、借用チェッカーより前 — なぜなら不正なプログラムのコンパイルを防ぐことは、ランタイムでデバッグするよりコストが低いからだ。