Rustにおける型とは何か

Essential
最終更新: タグ: Rust

前提知識

は約束だ。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)**は言語に組み込まれている:整数族と浮動小数点族、boolchar、そしてユニット型 ()。そのサイズと操作は言語仕様で固定されている。

ユーザー定義型(user-defined types)structenum、そしてそれらを組み合わせた型 — はそれ以外のすべてだ。コンパイラはこれらを同じ精度で扱う:u32 のサイズ、レイアウト、利用可能な操作を知るのと同様に、ユーザー定義型についても知っている。

この対称性は意図的だ。自分の型を定義することは、組み込み型を使うことと同等に第一級(first-class)扱いだ。Point 構造体(struct)、Color 列挙型(enum)、標準ライブラリの Vec<u8> — すべて同じ型システムの中に、対等な立場で存在する。

RustとZigの型の違い

Zigも静的型付けでネイティブコードにコンパイルされる。しかしZigはジェネリックパラメータに anytype を持ち、@bitCast によるバイトレベルの再解釈は日常的な操作だ。Zigは「ここでの本当の型は私が知っている、信じてくれ」と言う手段を提供している。

Rustはその取引を難しくしている。as キャストは定義された数値変換をカバーするが、無関係な型間でバイトを再解釈することはしない。生バイトの再解釈には unsafestd::mem::transmute が必要だ — あなたが全責任を負うという明示的で可視的な宣言だ。一般的なやり方は型システムの中に留まり、コンパイラにミスを検出させることだ。

実践的な意味

Rustの関数シグネチャを読むと情報が得られる:

fn area(width: f64, height: f64) -> f64 { width * height }

入力が64ビット浮動小数点数であること、出力も64ビット浮動小数点数であること、他の型は関与しないことがわかる。コンパイラはすべての呼び出し箇所でこれを検証する。u32 を黙って渡して暗黙変換された結果を得ることはできない。

この厳格さは意図的だ。型システムはRustの第一の防衛線だ — 所有権より前、借用チェッカーより前 — なぜなら不正なプログラムのコンパイルを防ぐことは、ランタイムでデバッグするよりコストが低いからだ。