演算子とオーバーロード

Rustは制限された形式での演算子オーバーロードを提供しており、オーバーロード可能な演算子がいくつか存在します。 型同士の間の演算子をサポートするためのトレイトが存在し、それらを実装することで演算子をオーバーロードすることができます。

例えば、 + の演算子は Add トレイトを利用することでオーバーロードすることができます:

use std::ops::Add; #[derive(Debug)] struct Point { x: i32, y: i32, } impl Add for Point { type Output = Point; fn add(self, other: Point) -> Point { Point { x: self.x + other.x, y: self.y + other.y } } } fn main() { let p1 = Point { x: 1, y: 0 }; let p2 = Point { x: 2, y: 3 }; let p3 = p1 + p2; println!("{:?}", p3); }
use std::ops::Add;

#[derive(Debug)]
struct Point {
    x: i32,
    y: i32,
}

impl Add for Point {
    type Output = Point;

    fn add(self, other: Point) -> Point {
        Point { x: self.x + other.x, y: self.y + other.y }
    }
}

fn main() {
    let p1 = Point { x: 1, y: 0 };
    let p2 = Point { x: 2, y: 3 };

    let p3 = p1 + p2;

    println!("{:?}", p3);
}

main 中で、2つの Point に対して + を使うことができます、 これは Point に対して Add<Output=Point> を実装したためです。

同じ方法でオーバーロード可能な演算子が多数あります、 それらに対応したトレイトは std::ops モジュール内に存在します。 全てのオーバーロード可能な演算子と対応するトレイトについては std::ops のドキュメントを読んで確認して下さい。

それらのトレイトの実装はパターンに従います。 Add トレイトを詳しく見ていきましょう:

fn main() { mod foo { pub trait Add<RHS = Self> { type Output; fn add(self, rhs: RHS) -> Self::Output; } } }
pub trait Add<RHS = Self> {
    type Output;

    fn add(self, rhs: RHS) -> Self::Output;
}

関連する3つの型が存在します: impl Add を実装するもの、 デフォルトが SelfRHS、 そして Output 。 例えば、式 let z = x + y においては xSelfy は RHS、 zSelf::Output 型となります。

fn main() { struct Point; use std::ops::Add; impl Add<i32> for Point { type Output = f64; fn add(self, rhs: i32) -> f64 { // // add an i32 to a Point and get an f64 // i32をPointに加算しf64を返す 1.0 } } }
impl Add<i32> for Point {
    type Output = f64;

    fn add(self, rhs: i32) -> f64 {
        // i32をPointに加算しf64を返す
    }
}

上のコードによって以下の様に書けるようになります:

fn main() { let p: Point = // ... let x: f64 = p + 2i32; }
let p: Point = // ...
let x: f64 = p + 2i32;

オペレータトレイトをジェネリック構造体で使う

オペレータトレイトがどのように定義されているかについて学びましたので、 トレイトについての章HasArea トレイトと Square 構造体をさらに一般的に定義することができます:

use std::ops::Mul; trait HasArea<T> { fn area(&self) -> T; } struct Square<T> { x: T, y: T, side: T, } impl<T> HasArea<T> for Square<T> where T: Mul<Output=T> + Copy { fn area(&self) -> T { self.side * self.side } } fn main() { let s = Square { x: 0.0f64, y: 0.0f64, side: 12.0f64, }; println!("Area of s: {}", s.area()); }
use std::ops::Mul;

trait HasArea<T> {
    fn area(&self) -> T;
}

struct Square<T> {
    x: T,
    y: T,
    side: T,
}

impl<T> HasArea<T> for Square<T>
        where T: Mul<Output=T> + Copy {
    fn area(&self) -> T {
        self.side * self.side
    }
}

fn main() {
    let s = Square {
        x: 0.0f64,
        y: 0.0f64,
        side: 12.0f64,
    };

    println!("Area of s: {}", s.area());
}

HasAreaSquare について、型パラメータ T を宣言し f64 で置換しました。 impl はさらに関連するモディフィケーションを必要とします:

fn main() { impl<T> HasArea<T> for Square<T> where T: Mul<Output=T> + Copy { ... } }
impl<T> HasArea<T> for Square<T>
        where T: Mul<Output=T> + Copy { ... }

area メソッドは辺を掛けることが可能なことを必要としています。 そのため型 Tstd::ops::Mul を実装していなければならないと宣言しています。 上で説明した Add と同様に、MulOutput パラメータを取ります: 数値を掛け算した時に型が変わらないことを知っていますので、 OutputT と設定します。 また T は、Rustが self.side を返り値にムーブするのを試みないようにコピーをサポートしている必要があります。