二項演算子のオーバーロードにおいて、左辺と右辺の交換法則をスマートに記述できない場合がある。 ここでは、その問題とそれに対するやや力技の解決策を紹介している。
演算子オーバーロードを使うと、型に対する演算子の挙動を定義できる。
例えば、組込の数値型 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
トレイトを実装する型に対して一つ一つ個別に定義していく。
この方法は演算は普段通り行えるが、未知の型に対応できない。
以下では、コード量を減らすためにマクロ 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);
ラッパー型を導入して孤児ルールに抵触しないようにする。
この方法は演算前に左辺のラップが必要になるが、未知の型にも対応できる。
以下では、ラッパー型 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
}
}