Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

ジェネリクス

型をパラメータ化する「ジェネリクス」について学びます。

ジェネリクスとは

ジェネリクスは型を抽象化し、同じコードを複数の型で使えるようにします。

#![allow(unused)]
fn main() {
// i32用
fn largest_i32(list: &[i32]) -> i32 { ... }

// f64用
fn largest_f64(list: &[f64]) -> f64 { ... }

// ジェネリクスで1つに
fn largest<T>(list: &[T]) -> T { ... }
}

関数のジェネリクス

fn largest<T: PartialOrd + Copy>(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));
}

型パラメータの慣習

  • T - Type(一般的な型)
  • E - Error(エラー型)
  • K, V - Key, Value(マップ用)
  • R - Result(結果型)

構造体のジェネリクス

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 };
}

複数の型パラメータ

struct Point<T, U> {
    x: T,
    y: U,
}

fn main() {
    let mixed = Point { x: 5, y: 4.0 };
}

ジェネリクス構造体のメソッド

#![allow(unused)]
fn main() {
struct Point<T> {
    x: T,
    y: T,
}

impl<T> Point<T> {
    fn x(&self) -> &T {
        &self.x
    }
}

// 特定の型にのみ実装
impl Point<f64> {
    fn distance_from_origin(&self) -> f64 {
        (self.x.powi(2) + self.y.powi(2)).sqrt()
    }
}
}

列挙型のジェネリクス

標準ライブラリのOptionResultがまさにこれです。

#![allow(unused)]
fn main() {
enum Option<T> {
    Some(T),
    None,
}

enum Result<T, E> {
    Ok(T),
    Err(E),
}
}

自作のジェネリクス列挙型

enum Response<T> {
    Success(T),
    Loading,
    Error(String),
}

fn main() {
    let user_response: Response<String> = Response::Success(String::from("太郎"));
    let count_response: Response<i32> = Response::Success(42);
}

トレイト境界

ジェネリクスに「このトレイトを実装している型のみ」という制約をつけます。

#![allow(unused)]
fn main() {
use std::fmt::Display;

fn print_info<T: Display>(item: T) {
    println!("情報: {}", item);
}
}

複数のトレイト境界

#![allow(unused)]
fn main() {
fn compare_and_display<T: PartialOrd + Display>(a: T, b: T) {
    if a > b {
        println!("{} > {}", a, b);
    } else {
        println!("{} <= {}", a, b);
    }
}
}

where句

境界が複雑な場合に読みやすくなります。

#![allow(unused)]
fn main() {
fn some_function<T, U>(t: T, u: U) -> i32
where
    T: Display + Clone,
    U: Clone + Debug,
{
    // ...
}
}

実践例: ジェネリックなペア

#[derive(Debug)]
struct Pair<T> {
    first: T,
    second: T,
}

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

    fn swap(&mut self) {
        std::mem::swap(&mut self.first, &mut self.second);
    }
}

impl<T: PartialOrd + Display> Pair<T> {
    fn cmp_display(&self) {
        if self.first > self.second {
            println!("最大は {}", self.first);
        } else {
            println!("最大は {}", self.second);
        }
    }
}

fn main() {
    let mut pair = Pair::new(5, 10);
    pair.cmp_display();
    pair.swap();
    println!("{:?}", pair);
}

ジェネリクスとパフォーマンス

Rustのジェネリクスは単相化(Monomorphization)されます。

#![allow(unused)]
fn main() {
// このコードは
fn largest<T: PartialOrd>(a: T, b: T) -> T { ... }
largest(5, 10);
largest(1.0, 2.0);

// コンパイル時にこうなる
fn largest_i32(a: i32, b: i32) -> i32 { ... }
fn largest_f64(a: f64, b: f64) -> f64 { ... }
}

つまり、実行時のコストはゼロです!

標準ライブラリのジェネリクス例

#![allow(unused)]
fn main() {
// Vec<T>
let v: Vec<i32> = vec![1, 2, 3];
let v: Vec<String> = vec![String::from("a")];

// HashMap<K, V>
use std::collections::HashMap;
let mut map: HashMap<String, i32> = HashMap::new();

// Option<T>
let some: Option<i32> = Some(5);

// Result<T, E>
let result: Result<i32, String> = Ok(42);
}

まとめ

概念説明
ジェネリクス型をパラメータ化
<T>型パラメータ
トレイト境界T: Traitで制約を追加
where複雑な境界を読みやすく
単相化コンパイル時に具体型に展開(ゼロコスト)

確認テスト

Q1. ジェネリクスの主な目的は?

Q2. fn foo<T: Clone + Debug>(x: T)のトレイト境界が意味するのは?

Q3. 以下のコードの出力は?
struct Container<T> { value: T } impl<T> Container<T> { fn new(value: T) -> Self { Container { value } } fn get(&self) -> &T { &self.value } } let c = Container::new(42); println!("{}", c.get());

Q4. fn print_largest<T>(a: T, b: T)a > bを使うために必要なトレイト境界は?

Q5. Rustのジェネリクスの「単相化(Monomorphization)」とは?


次のドキュメント: 05_modules.md