第12章: ジェネリクス

学習目標

  • ジェネリクスの概念と利点を理解する
  • ジェネリックな関数と構造体を定義する
  • 型パラメータの使い方を学ぶ
  • ジェネリクスとトレイト境界の組み合わせを理解する

---

12.1 ジェネリクスの基本

12.1.1 なぜジェネリクスが必要か

同じロジックを異なる型で再利用したい場合:

// ジェネリクスなしの場合
fn largest_i32(list: &[i32]) -> &i32 {
    let mut largest = &list[0];
    for item in list {
        if item > largest {
            largest = item;
        }
    }
    largest
}

fn largest_char(list: &[char]) -> &char {
    let mut largest = &list[0];
    for item in list {
        if item > largest {
            largest = item;
        }
    }
    largest
}

// ジェネリクスを使用
fn largest<T: PartialOrd>(list: &[T]) -> &T {
    let mut largest = &list[0];
    for item in list {
        if item > largest {
            largest = item;
        }
    }
    largest
}

fn main() {
    let numbers = vec![34, 50, 25, 100, 65];
    println!("最大値: {}", largest(&numbers));

    let chars = vec!['y', 'm', 'a', 'q'];
    println!("最大: {}", largest(&chars));
}

12.1.2 型パラメータの命名規則

慣例として、短い名前を使用:

  • T - Type(一般的な型)
  • E - Error(エラー型)
  • K, V - Key, Value(キーと値)
  • R - Result(結果型)
  • 12.2 ジェネリックな構造体

    12.2.1 単一の型パラメータ

    struct Point<T> {
        x: T,
        y: T,
    }
    
    fn main() {
        let integer_point = Point { x: 5, y: 10 };
        let float_point = Point { x: 1.0, y: 4.0 };
    
        println!("整数: ({}, {})", integer_point.x, integer_point.y);
        println!("浮動小数点: ({}, {})", float_point.x, float_point.y);
    }
    

    12.2.2 複数の型パラメータ

    struct Point<T, U> {
        x: T,
        y: U,
    }
    
    fn main() {
        let mixed = Point { x: 5, y: 4.0 };
        println!("混合: ({}, {})", mixed.x, mixed.y);
    }
    

    12.3 ジェネリックなメソッド

    12.3.1 implブロックでのジェネリクス

    struct Point<T> {
        x: T,
        y: T,
    }
    
    impl<T> Point<T> {
        fn x(&self) -> &T {
            &self.x
        }
    
        fn y(&self) -> &T {
            &self.y
        }
    }
    
    // 特定の型に対してのみ実装
    impl Point<f64> {
        fn distance_from_origin(&self) -> f64 {
            (self.x.powi(2) + self.y.powi(2)).sqrt()
        }
    }
    
    fn main() {
        let p = Point { x: 3.0, y: 4.0 };
        println!("距離: {}", p.distance_from_origin());
    }
    

    12.4 ジェネリクスとトレイト境界

    12.4.1 トレイト境界による制約

    use std::fmt::Display;
    
    fn print_pair<T: Display, U: Display>(a: T, b: U) {
        println!("({}, {})", a, b);
    }
    
    // where句を使った記法
    fn print_pair_where<T, U>(a: T, b: U)
    where
        T: Display,
        U: Display,
    {
        println!("({}, {})", a, b);
    }
    
    fn main() {
        print_pair(1, "hello");
        print_pair_where(3.14, 'x');
    }
    

    まとめ

  • ジェネリクスはコードの再利用性を高める
  • 型パラメータで異なる型に同じロジックを適用できる
  • トレイト境界で型パラメータに制約を付けられる
  • コンパイル時に具体的な型が決定されるため、実行時オーバーヘッドがない