はじめに
既存のC++プログラムをRustを用いて書き換えている(仕事ではなく)。このプログラムはテンプレートを多用しているため、Rustにおけるテンプレート代替機能であるgenericsを利用することになる。C++にはテンプレートの特殊化と呼ばれる機能があるが、Rustにはそれに相当する機能がない。どのようにすれば、当該機能を実現できるのか調べたので紹介したい。
Rustが特殊化をサポートしない理由はあるだろうし(もしかしてこれからサポートするのかしら)、C++の思想のままRustで実装するべきでないという主張はもっともだと思う。今回は、ひとつの余興である。
C++におけるtemplateの特殊化
まず最初にC++のtemplate特殊化の典型例を以下に示す(ファイル名:point.h)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
#pragma once template<typename T, size_t D> struct Point; template<typename T> struct Point<T, 2> { T x; T y; Point(const T& x, const T& y) : x{ x }, y{ y } {} }; template<typename T> struct Point<T, 3> { T x; T y; T z; Point(const T& x, const T& y, const T& z) : x{ x }, y{ y }, z{ z } {} }; |
これを使う側のmain.cppの実装は以下の通り。
1 2 3 4 5 6 7 |
#include "point.h" int main(int args, const char* argv[]) { auto x2 = Point<int, 2>{1,2}; auto x3 = Point<int, 3>{1, 2, 3}; return 0; } |
template特殊化とは、具体的なtemplate引数を与えた場合の中身を個別に定義する仕組みである。上の例の場合、次元数を表すtemplate引数D
について、これが2の場合と3の場合を定義している(D
が4の場合は構造体は未定義となりコンパイルエラーになる)。同じことをRustでもしたい。
Rustの場合
Rustの場合、templateに相当する機能はgenericsである。genericsは特殊化をサポートしないが、後述する仕組みを使うと以下の呼び出し側のコードを実現できる。
1 2 3 4 5 6 7 |
mod point; use point::{Point, TwoDim, ThreeDim}; fn main() { let x2 = Point::<i32, TwoDim>::new(1, 2); let x3 = Point::<i32, ThreeDim>::new(1, 2, 3); } |
new
はC++のコンストラクタに相当する関数である。
種明かし
まず最初に2次元の場合の構造体と3次元の場合の構造体を定義し、それぞれについてnew
関数を定義する。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
pub struct Point2<T> { pub x: T, pub y: T, } pub struct Point3<T> { pub x: T, pub y: T, pub z: T, } impl<T> Point2<T> { pub fn new(x:T, y:T) -> Self { Self {x, y} } } impl<T> Point3<T> { pub fn new(x:T, y:T, z:T) -> Self { Self {x, y, z} } } |
次元の振り分けを行うため次を定義する。
1 2 3 4 5 6 |
pub trait Type<T> { type Point; } pub struct TwoDim; pub struct ThreeDim; |
そして、2つの構造体TwoDim
とThreeDim
のそれぞれに対しType
の中身を実装する。
1 2 3 4 5 6 7 |
impl<T> Type<T> for TwoDim { type Point = Point2<T>; } impl<T> Type<T> for ThreeDim { type Point = Point3<T>; } |
最後に次を定義すれば完成する。
1 |
pub type Point<T, D> = <D as Type<T>>::Point; |
今回の全コードはここにある。
まとめ
どうやるんだろうとネットを漁っていたところ、上の回答を見つけた(少し変更してある)。なるほど。RustのgenericsはC++のtemplate以上に引数に課される条件が厳しく、しばしば身動きできなくなる。まだまだ修行が足りない(笑)。