Rustで参照を引数にとる演算子オーバーロード

2021/03/21に公開

注:この記事は昔Qiitaで公開していた記事を引っ越してきたものです。

話はいいからコード見せろという方はここ参照。

以下ではRustのバージョンは1.24.0 (stable)とする。

はじめに

Rustで演算子オーバーロードする場合、基本的には https://doc.rust-lang.org/std/ops/ のサンプルコードを真似て書けば良い。しかし、演算子の引数に与えた変数もMoveされてしまうため引き続き変数を使いたい場合にはclone()しなければならない(Copyトレイトを実装していない場合)。これでは、無駄なコピーが発生するため引数を参照にしたいが苦戦したのでメモしておく。

通常の演算子オーバーロード

通常の演算子オーバーロードは以下のように書ける。

use std::ops::{Add, AddAssign};
#[derive(Debug, Clone)]
struct Point {
    x: i64,
    y: i64,
    z: i64,
}

impl Add for Point {
    type Output = Point;
    fn add(self, other: Point) -> Point {
        Point {
            x: self.x + other.x,
            y: self.y + other.y,
            z: self.z + other.z,
        }
    }
}

impl AddAssign for Point {
    fn add_assign(&mut self, other: Point) {
        self.x += other.x;
        self.y += other.y;
        self.z += other.z;
    }
}

fn main() {
    let p = Point { x: 3, y: 4, z: 5 };
    let q = Point { x: 5, y: 12, z: 13 };
    let mut r = Point { x: 1, y: 2, z: 3 };
    println!("r={:?}", r);
    r += p.clone();
    println!("r={:?}", r);
    println!("p+q={:?}", p.clone() + q.clone());
    println!("p={:?}, q={:?}", p, q);
}

Addを参照に変更

Addの方を参照に変えるにはfn add(self, other: Point) -> Pointfn add(&self, other: &Point) -> Pointにすればいいように思えるが、これではうまく行かない。調べると https://stackoverflow.com/questions/28005134/how-do-i-implement-the-add-trait-for-a-reference-to-a-struct が見つかったが、この回答そのままでは動かなかったため、以下のように少し手を加えた。

use std::ops::{Add, AddAssign};
#[derive(Debug, Clone)]
struct Point {
    x: i64,
    y: i64,
    z: i64,
}

impl<'a> Add for &'a Point {
    type Output = Point;
    fn add(self, other: &Point) -> Point {
        Point {
            x: self.x + other.x,
            y: self.y + other.y,
            z: self.z + other.z,
        }
    }
}

impl AddAssign for Point {
    fn add_assign(&mut self, other: Point) {
        self.x += other.x;
        self.y += other.y;
        self.z += other.z;
    }
}

fn main() {
    let p = Point { x: 3, y: 4, z: 5 };
    let q = Point { x: 5, y: 12, z: 13 };
    let mut r = Point { x: 1, y: 2, z: 3 };
    println!("r={:?}", r);
    r += p.clone();
    println!("r={:?}", r);
    println!("p+q={:?}", &p + &q);
    println!("p={:?}, q={:?}", p, q);
}

AddAssignを参照に変更

Addの引数を参照に変えたときと同様にすればいいと思えるが、実はこれではうまく行かない。色々と調べた結果 https://doc.rust-lang.org/src/alloc/string.rs.html#1836-1841 を参考に書いたところうまく動いた。以下にコードを示す。

use std::ops::{Add, AddAssign};
#[derive(Debug, Clone)]
struct Point {
    x: i64,
    y: i64,
    z: i64,
}

impl<'a> Add for &'a Point {
    type Output = Point;
    fn add(self, other: &Point) -> Point {
        Point {
            x: self.x + other.x,
            y: self.y + other.y,
            z: self.z + other.z,
        }
    }
}

impl<'a> AddAssign<&'a Point> for Point {
    fn add_assign(&mut self, other: &Point) {
        self.x += other.x;
        self.y += other.y;
        self.z += other.z;
    }
}

fn main() {
    let p = Point { x: 3, y: 4, z: 5 };
    let q = Point { x: 5, y: 12, z: 13 };
    let mut r = Point { x: 1, y: 2, z: 3 };
    println!("r={:?}", r);
    r += &p;
    println!("r={:?}", r);
    println!("p+q={:?}", &p + &q);
    println!("p={:?}, q={:?}", p, q);
}

おわりに

Rustは勉強中なのでより良い方法などありましたら教えて下さると幸いです。

Discussion