二項演算子のオーバーロードにおいて、左辺と右辺の交換法則をスマートに記述できない場合がある。 ここでは、その問題とそれに対するやや力技の解決策を紹介している。

基礎知識

演算子オーバーロード

演算子オーバーロードを使うと、型に対する演算子の挙動を定義できる。

例えば、組込の数値型 i32 では +, -, *, / などが定義されている。

そして、ユーザが作成した独自型についても、組込型と同様、これらを定義できる。

通常パターン

殆どの場合、特に問題なく交換法則を定義できる。

サンプル

以下では、* 演算子をトレイト i32 と型 P3<i32> とで定義をしようとしている。


use std::fmt::Debug;
use std::ops::Mul;

fn main() {
    let (op_x, op_y) = (P3(1, 2, 3), 2);
    assert_eq!(op_x * op_y, P3(2, 4, 6));
    assert_eq!(op_y * op_x, P3(2, 4, 6));
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
struct P3<T>(T, T, T);

impl Mul<i32> for P3<i32> {
    type Output = P3<i32>;

    fn mul(self, rhs: i32) -> Self::Output {
        let v1 = self.0 * rhs;
        let v2 = self.1 * rhs;
        let v3 = self.2 * rhs;
        P3(v1, v2, v3)
    }
}

impl Mul<P3<i32>> for i32 {
    type Output = P3<i32>;

    fn mul(self, rhs: P3<i32>) -> Self::Output {
        rhs * self
    }
}

問題点

クレート固有の型、トレイトでまとめた型、両者を演算対象として演算子をオーバーロードしたいとする。この時、左辺が前者で右辺が後者のパターンは問題ないが、左辺が後者で右辺が前者のパターンでは、孤児ルールによりエラー E0210 が発生する。

サンプル

以下では、* 演算子をトレイト Scalar と型 P3<T> とで定義をしようとしている。
しかし、Scalar を左辺とするパターンの定義でエラーになってしまっている。


use std::fmt::Debug;
use std::ops::Mul;

fn main() {
    let (op_x, op_y) = (P3(1, 2, 3), 2);
    assert_eq!(op_x * op_y, P3(2, 4, 6));
    assert_eq!(op_y * op_x, P3(2, 4, 6));
}

trait Scalar: Clone + Copy + Sized + Mul<Output = Self> {}
impl<T> Scalar for T where T: Clone + Copy + Sized + Mul<Output = Self> {}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
struct P3<T>(T, T, T);

impl<T> Mul<T> for P3<T>
where
    T: Scalar,
{
    type Output = P3<T>;

    fn mul(self, rhs: T) -> Self::Output {
        let v1 = self.0 * rhs;
        let v2 = self.1 * rhs;
        let v3 = self.2 * rhs;
        P3(v1, v2, v3)
    }
}

impl<T> Mul<P3<T>> for T
where
    T: Scalar,
{
    type Output = P3<T>;

    fn mul(self, rhs: P3<T>) -> Self::Output {
        rhs * self
    }
}

error[E0210]: type parameter `T` must be covered by another type when it appears before the first local type (`P3<_>`)
  --> src\main.rs:27:6
   |
27 | impl<T> Mul<P3<T>> for T
   |      ^ type parameter `T` must be covered by another type when it appears before the first local type (`P3<_>`)
   |
   = note: implementing a foreign trait is only possible if at least one of the types for which it is implemented is local, and no uncovered type parameters appear before that first local type
   = note: in this case, 'before' refers to the following order: `impl<..> ForeignTrait<T1, ..., Tn> for T0`, where `T0` is the first and `Tn` is the last

解決策

解決策 1

型ごとの個別定義

トレイトを実装する型に対して一つ一つ個別に定義していく。

この方法は演算は普段通り行えるが、未知の型に対応できない。

サンプル

以下では、コード量を減らすためにマクロ define_mul_p3 を導入している。


use std::fmt::Debug;
use std::ops::Mul;

fn main() {
    let (op_x, op_y) = (P3(1, 2, 3), 2);
    assert_eq!(op_x * op_y, P3(2, 4, 6));
    assert_eq!(op_y * op_x, P3(2, 4, 6));
}

trait Scalar: Clone + Copy + Sized + Mul<Output = Self> {}
impl<T> Scalar for T where T: Clone + Copy + Sized + Mul<Output = Self> {}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
struct P3<T>(T, T, T);

impl<T> Mul<T> for P3<T>
where
    T: Scalar,
{
    type Output = P3<T>;

    fn mul(self, rhs: T) -> Self::Output {
        let v1 = self.0 * rhs;
        let v2 = self.1 * rhs;
        let v3 = self.2 * rhs;
        P3(v1, v2, v3)
    }
}

macro_rules! define_mul_p3 {
    ($type:ty) => {
        impl Mul<P3<$type>> for $type {
            type Output = P3<$type>;
        
            fn mul(self, rhs: P3<$type>) -> Self::Output {
                rhs * self
            }
        }                
    };
}

define_mul_p3!(i32);
define_mul_p3!(i64);
define_mul_p3!(f32);
define_mul_p3!(f64);

解決策 2

ラッパー型の導入

ラッパー型を導入して孤児ルールに抵触しないようにする。

この方法は演算前に左辺のラップが必要になるが、未知の型にも対応できる。

サンプル

以下では、ラッパー型 Lhs を導入している。


use std::fmt::Debug;
use std::ops::Mul;

fn main() {
    let (op_x, op_y) = (P3(1, 2, 3), 2);
    assert_eq!(op_x * op_y, P3(2, 4, 6));
    assert_eq!(Lhs(op_y) * op_x, P3(2, 4, 6));
}

struct Lhs<T>(T);

trait Scalar: Clone + Copy + Sized + Mul<Output = Self> {}
impl<T> Scalar for T where T: Clone + Copy + Sized + Mul<Output = Self> {}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
struct P3<T>(T, T, T);

impl<T> Mul<T> for P3<T>
where
    T: Scalar,
{
    type Output = P3<T>;

    fn mul(self, rhs: T) -> Self::Output {
        let v1 = self.0 * rhs;
        let v2 = self.1 * rhs;
        let v3 = self.2 * rhs;
        P3(v1, v2, v3)
    }
}

impl<T> Mul<P3<T>> for Lhs<T>
where
    T: Scalar,
{
    type Output = P3<T>;

    fn mul(self, rhs: P3<T>) -> Self::Output {
        rhs * self.0
    }
}