Posted at

Rustのマクロとstructを組み合わせて使う

More than 3 years have passed since last update.

または数値traitみたいな物が欲しい場合について。

以前structとtraitの基本的な使い方を以下のPointという構造体を作って紹介した

が、その続き。

pub struct Point<T> {

pub x: T,
pub y: T
}

基本的には前回定義したように抽象的なtraitを組み合わせて

汎用的な構造体を構築するのが正道だと思うが、

そうは言っても特定の型へのキャストが必要になるときに困る事がある。


現状のtraitを組み合わせた方法の限界

具体的にはユークリッド距離を返すnormメソッドを想定する。

ユークリッド距離に関しては平方根をとる事になるのでので

float系の何かの型へのキャストが必要になる。

impl<T> Point<T> where T:Mul+Copy,<T as Mul>::Output:Add {

pub fn norm(self) -> f64 {
((self.x*self.x+self.y*self.y) as f64).sqrt()
}
}

以上のように前回の知識で素朴に実装しようとすると以下のように

error: non-scalar castといって怒られてしまう事になる。

src/foo.rs:33:10: 33:46 error: non-scalar cast: `<<T as core::ops::Mul>::Output as core::ops::Add>::Output` as `f64`

src/foo.rs:33 ((self.x*self.x+self.y*self.y) as f64).sqrt()

これは<<T as core::ops::Mul>::Output as core::ops::Add>::Output

scalarな値だとは限らないので当然といえば当然だが、残念。

scalarなキャストが保証可能を表現する数値traitみたいなものがあればいいような気がするが

そんなものはなかった。


macro_rules!

そこでmacro_rules!の出番となる。

要はf64にキャストできそうな数値型はマクロをつかって全部定義しちゃおうという発想

macro_rules! norm_impl {

($($t:ty)*) => ($(
impl Point<$t> {
pub fn norm(self) -> f64 {
((self.x*self.x+self.y*self.y) as f64).sqrt()
}
}
)*)
}
norm_impl! { usize u8 u16 u32 u64 isize i8 i16 i32 i64 f32 f64 }

macro_rules!の詳しい記法については

公式ドキュメントが一番まとまっているので読んでもらうとして、

簡単に解説すると=>の左部分はマクロが受け取る引数の部分で、

$()*で囲む部分が引数の繰り返し部分になり、

=>の右側で$()*で囲った部分がキャプチャした引数分展開され繰り返される。

この場合impl Point<$t> {のあたりから5行が展開される。

rustのマクロは展開後の形が想像しやすいし

キャプチャする部分の型も存在するのでかなり人類にも扱いやすいという印象。

もちろん、マクロなので多用すると人智を超えた複雑さを孕んで行く事になるし、

これがいいやり方なのかは議論の余地があると思うが、

数値のプリミティブをマクロで一気に実装しちゃう手法は

stdライブラリのAddとかMulとかの実装にも使われている。

https://doc.rust-lang.org/src/core/ops.rs.html

この辺結構プラグマティックな感じ。


まとめ

rustには数値traitや整数traitや浮動小数traitみたいなのが現状ないので

macro_rules!で解決しましょうという話でした。