macroを使ってopを実装する

TL;DR

構造体に対して演算子を実装するときに、マクロを使わないとすごく長くなるのでマクロの使い方についてまとめておく。

macroの基本

マクロの引数は

意味
blockブロック
expr1+1
stmt
patパターン
ty
ident識別子
path修飾された名前。T::deafult
tt単一のトークン木だいたいなんでも。演算子もここ。
metaアトリビュートの中身。cfg(target_os = "windows")

で、今回使うのはidenttt。ポイントは演算子を入れる引数はttが使えるということ。RFC#426とかでop用の引数を作ろうみたいな話もあるらしいが、特に進んでなさそう。例えば以下のような感じで演算子を使える。

macro_rules! test_op {
    ($op: tt) => {
        1 $op 2
    };
}

#[test]
fn test_op() {
    assert_eq!(3, test_op!(+));
    assert_eq!(-1, test_op!(-));
    assert_eq!(2, test_op!(*));
    assert_eq!(0, test_op!(/));
    assert_eq!(false, test_op!(==));
}

今回は以下のような単純な構造体を考える。

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

これに対して、Point<T><T>に関する四則演算を定義する。

例えば、Addの場合は以下のようになる。

use std::ops::*;

impl<T> Add<Point<T>> for Add<T>
    where T: Add<Output = T>
{
    type Output = Self;
    fn add(self, rhs: Self) -> Self {
        Self {
            x: self.x + rhs.x,
            y: self.y + rhs.y,
        }
    }
}
impl<T> Add<T> for Point<T>
    where T: Add<Output = T> + Copy
{
    type Output = Self;
    fn add(self, rhs: T) -> Self {
        Self {
            x: self.x + rhs.
            y: self.y + rhs,
        }
    }
}

これらの処理は他の四則演算においてもだいたい同じコードなのでマクロでまとめたい。基本的には必要なtraitと、traitに必要な関数名、演算子を指定すればいい。

macro_rules! impl_point_op {
    ($trait: ident, $function: ident, $op: tt) => {
        impl<T> $trait<Point<T>> for Point<T>
        where
            T: $trait<Output = T>,
        {
            type Output = Self;
            fn $function(self, rhs: Self) -> Self {
                Self {
                    x: self.x $op rhs.x,
                    y: self.y $op rhs.y,
                }
            }
        }

        impl<T> $trait<T> for Point<T>
        where
            T: $trait<Output = T> + Copy,
        {
            type Output = Self;
            fn $function(self, rhs: T) -> Self {
                Self {
                    x: self.x $op rhs,
                    y: self.y $op rhs,
                }
            }
        }
    };
}

impl_point_op!(Add, add, +);
impl_point_op!(Sub, sub, -);
impl_point_op!(Mul, mul, *);
impl_point_op!(Div, div, /);

演算子をどう使えばいいのかわからなかったのでメモがてらまとめておいた。

この記事に関するIssueをGithubで作成する

Read Next