Pebble Coding

ソフトウェアエンジニアによるIT技術、数学の備忘録

rustのtraitで値バージョンと借用バージョンの両方の実装方法

rustの演算子オーバーロードをする際、値と借用の両方の実装を行う方法です。

次の構造体の掛け算を考えます。

#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Unit {
    pub coef: BigInt,
    pub xpow: BigInt,
    pub ypow: BigInt,
}

値の掛け算と借用の掛け算のテストを先に書いておきます。

#[test]
fn unit_test() {
    let u2 = Unit {
        coef: BigInt::from(4),
        .. Default::default()
    };  
    let u3 = Unit {
        coef: BigInt::from(3),
        .. Default::default()
    };  
    assert_eq!((u2.clone() * u3.clone()).to_string(), "12"); // 値
    assert_eq!((&u2 * &u3).to_string(), "12"); // 借用
}

値の掛け算は次のように実装します。

use std::ops::Mul;
impl Mul for Unit {
    type Output = Unit;
    fn mul(self, other: Self) -> Self {
        Unit {
            coef: &self.coef * &other.coef,
            xpow: &self.xpow + &other.xpow,
            ypow: &self.ypow + &other.ypow,
        }
    }
}

これだけだと、借用の掛け算の実装がないので、以下のエラーが表示されます。

error[E0369]: binary operation `*` cannot be applied to type `&unit::Unit`
   --> src/unit.rs:236:21
    |
236 |     assert_eq!((&u2 * &u3).to_string(), "12");
    |                 --- ^ --- &unit::Unit
    |                 |
    |                 &unit::Unit
    |
    = note: an implementation of `std::ops::Mul` might be missing for `&unit::Unit`

借用バージョンの実装を追加します。

impl<'a> Mul<&'a Unit> for &'a Unit {
    type Output = Unit;
    fn mul(self, other: Self) -> Unit {
        Unit {
            coef: &self.coef * &other.coef,
            xpow: &self.xpow + &other.xpow,
            ypow: &self.ypow + &other.ypow,
        }
    }
}

これで実装はできましたが、実装の中身はほぼ同じなので、これはマクロを使って一つにまとめられるはずですが、
やり方が分かりません。

なお、「左側が値、右側が借用」「左側が借用、右側が値」もあった方がいいですが、これもマクロでできる気がするのですが。。

impl_ops クレートを使う

impl_ops クレートを使うと、マクロで全てのパターンを実装してくれるようです。

impl_op_ex!(* |a: &Unit, b: &Unit| -> Unit {
    Unit {
        coef: &a.coef * &b.coef,
        xpow: &a.xpow + &b.xpow,
        ypow: &a.ypow + &b.ypow,
    }   
});

docs.rs