Chapter 09

ジェネリクス

mebiusbox
mebiusbox
2023.03.16に更新
このチャプターの目次

📌 ジェネリクス

Rust では,例えば数値演算をするときに,左値と右値の型が同じである必要があります.ここで,加算を行う add という関数を考えてみます.数値型には整数型や浮動小数点型などあります.型の数だけ add 関数を定義してしまうと同じコードが大量に出来てしまいます.そこで,引数の型が変わっても関数本体のコードが変わらない場合は,任意の型を受け取れる関数を定義することでコードの重複を避けることが出来ます.このような仕組みを ジェネリクス と言い,任意の型のことを ジェネリック型 と言います.

関数,構造体,列挙型でジェネリック型を使うには,それぞれの名前の後ろに <> でジェネリック型の名前を指定します.名前には慣例的に T をよく使います.また,複数であれば, , で列挙します.

fn add<T: std::ops::Add<Output=T> >(a: T, b: T) -> T {
    a+b
}

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

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

メソッドの定義では次のように記述します.

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

impl<T> Point<T> {
    fn xy(self) -> (T, T) {
        (self.x, self.y)
    }
}

impl の後ろに <T> を宣言しています.こうすることで Point<T>T がジェネリック型であることを明示しています.もし, impl<T> でなければ, Point<T>T はジェネリック型ではなく T という名前の型を指定することになってしまいます.これとは逆に,ジェネリック型に明示的な型を指定するやり方があります.

impl Point<f64> {
    fn distance(&self) -> f64 {
        (self.x.powi(2) + self.y.powi(2)).sqrt()
    }
}

ジェネリック型は任意の型を受け取りますが,静的型付け言語では,コンパイル時に型がわかるので,型に特化したコードが生成されます.このような仕組みを単相化(Monomorphzation)と言います.

Rust は強い型推論があるので,ジェネリック型に対して適切な型を自動で推論してくれます.しかし,明示的に指定したい場合もあります.この場合は::<...>演算子を使います.この演算子は魚が速く泳いでいるように見えることからturbofishと呼ばれています.

let point = Point::<f64>{x: 3., y: 5.};