📌 ジェネリクス
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.};