ジェネリクスを用いた関数の実装
ジェネリクスとトレイト境界
ある関数を複数の型に対して実装したい時にimpl Trait
を使用した静的ディスパッチがあるが、ジェネリクスを使用することでも静的ディスパッチを行うこともできる。この様に、「あるトレイトを実装しているか」という条件をトレイト境界と呼ぶ。
つまり?
ジェネリクスで関数を定義すると一つの関数を複数の型に対して使用することができ実装を減らすことができる。
ジェネリクス無しでの実装
例えば、以下の様な二次元と三次元ベクトルを表す構造体とそれのノルムを比較する実装があるとする。
もっと簡単に実装することもできるが今回は全ての実装を行う。
use std::cmp::Ordering;
struct Vector2D {
x: i32,
y: i32,
}
impl Vector2D {
fn new(x: i32, y: i32) -> Self {
Self { x, y }
}
fn norm(&self) -> i32 {
self.x * self.x + self.y * self.y
}
}
impl PartialEq for Vector2D {
fn eq(&self, other: &Self) -> bool {
self.norm() == other.norm()
}
}
impl PartialOrd for Vector2D {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Vector2D {
fn cmp(&self, other: &Self) -> Ordering {
self.norm().cmp(&other.norm())
}
}
struct Vector3D {
x: i32,
y: i32,
z: i32,
}
impl Vector3D {
fn new(x: i32, y: i32, z: i32) -> Self {
Self { x, y, z }
}
fn norm(&self) -> i32 {
self.x * self.x + self.y * self.y + self.z * self.z
}
}
impl PartialEq for Vector3D {
fn eq(&self, other: &Self) -> bool {
self.norm() == other.norm()
}
}
impl PartialOrd for Vector3D {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Vector3D {
fn cmp(&self, other: &Self) -> Ordering {
self.norm().cmp(&other.norm())
}
}
これを用いて、この構造体の配列からノルムが最大の要素を求める関数を実装するとする。
fn max(array: &[Vector2D]) -> &Vector2D {
let mut max = array[0];
for i in 1..array.len() {
if array[i] > *max {
max = array[i];
}
}
max
}
この関数は正しく機能するが、Vector2D
型にしか対応していない。もし、Vector3D
型にも対応したい場合は、それぞれの型に対してmax
関数を実装する必要がある。
fn main() {
let array = [
Vector3D::new(1, 2, 3),
Vector3D::new(4, 5, 6),
Vector3D::new(7, 8, 9),
];
println!("{:?}", max(&array)); // error
}
ジェネリクスを使用した実装
ジェネリクスを使用することで、Vector2D
型とVector3D
型に対して同じ関数を使用することができる。
+ fn max<T: Ord>(array: &[T]) -> &T {
let mut max = &array[0];
for i in 1..array.len() {
if array[i] > *max {
max = &array[i];
}
}
max
}
解説
<T: Ord>
?
T
はジェネリクスの型パラメータで、任意のタイプを表す。
なのでarray: &[T]
はT
型のスライスを表し、&T
はT
型の参照を表す。
Ord
?
T
型はOrd
トレイトを実装している必要があるという意味。今回のVector2D
とVector3D
はOrd
トレイトを実装しているので条件を満たしている。
おまけ
ジェネリクスについて
where
句を使用することでも同じことができる。
- fn max<T: Ord>(array: &[T]) -> &T {
+ fn max<T>(array: &[T]) -> &T where T: Ord {
let mut max = &array[0];
for i in 1..array.len() {
if array[i] > *max {
max = &array[i];
}
}
max
}
複数のトレイトを要求する時は+
を使用する。
fn max<T>(array: &[T]) -> &T
where
T: Ord + Display,
{
let mut max = &array[0];
for i in 1..array.len() {
if array[i] > *max {
max = &array[i];
}
}
println!("{}", max); // Displayトレイトを実装しているので使用できる
}
Traitの実装の自動化
derive
マクロを使用することで、PartialEq
やPartialOrd
などのトレイトを自動で実装することができる。
+ #[derive(PartialEq, PartialOrd, Ord)]
struct Vector2D {
x: i32,
y: i32,
}
これは非常に便利なので積極的に利用していきたい。ただし、自作のトレイトをderive
マクロに対応させるのは中々難しいので後日説明する。
Discussion