本文へスキップ
BecomeCoder

Rustコース · 第9章 ジェネリクスとトレイト · レッスン36

ジェネリクス ― 型を後から差し込む

ブラウザで完結

導入

「i32 の最大値を返す関数」と「f64 の最大値を返す関数」を別々に書くのは面倒です。中身のロジックは同じなのだから、型をパラメータにして1回だけ書きたい——それを叶えるのがジェネリクスです。

説明

型を <T> というパラメータにして、関数や構造体を「型に対して汎用的」にします。

// どんな型 T でも動く「ペア」構造体
struct Pair<T> {
    first: T,
    second: T,
}

impl<T> Pair<T> {
    fn new(first: T, second: T) -> Pair<T> {
        Pair { first, second }
    }
}

fn main() {
    let ints = Pair::new(1, 2);            // T = i32
    let words = Pair::new("a", "b");       // T = &str

    println!("{} {}", ints.first, words.second);
}
  • Pair<T>T は「あとで決まる型」のプレースホルダ。Pair::new(1, 2) なら T = i32Pair::new("a", "b") なら T = &str とコンパイラが決めます。
  • impl<T> Pair<T> … ジェネリックな型にメソッドを実装するときは impl<T> と書きます。

ここで大事なのは、ジェネリクスは実行時のコストがゼロだということです。Rust はコンパイル時に、実際に使われた型ごとに具体的なコードを生成します(単相化 / monomorphization)。Pair<i32>Pair<&str> はそれぞれ専用のコードに展開されるので、「汎用にしたから遅くなる」ことがありません。抽象化してもゼロコスト、という Rust の哲学がここにも表れています。

ただし、上の Pair はフィールドを保持するだけでした。もし関数の中で T の値どうしを「比較する」「足す」といった操作をしたくなったら? 「T はどんな型でもいい」だけでは、その型が比較や加算をできるか分かりません。そこで必要になるのが、次のレッスンのトレイトです。

試すには

Pair::new(1, "a") のように firstsecond で違う型を渡すとエラーになります(両方 T なので同じ型でなければならない)。違う型を許したいなら Pair<T, U> と2つの型パラメータにします。ジェネリクスが「型の約束」をどう表しているか感じてみましょう。

実際に動かしてみよう

下のエディタにRustを書いて「実行」を押すと、学習用シミュレータが println! の出力を表示します(本物のrustcではなく、教材の範囲を再現した軽量エンジンです)。struct・enum・match・トレイト・Vec/String/HashMap・Option/Result なども動きます。本文の例を書き換えて動かしてみましょう(所有権・借用チェッカーのコンパイルエラーは再現しないため、動きの確認用として使ってください)。

Rust — ブラウザ内で実行(学習用シミュレータ)

Rustの教材サブセットを動かす学習用シミュレータを読み込みます(本物のrustcではなく、動きを再現した軽量な自作エンジンです)。
スクロールして表示された時点でも自動で読み込まれます。