% Перегрузка операций

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);
}

В main мы можем использовать операцию + для двух Point, так как мы реализовали типаж Add<Output=Point> для Point.

Есть целый ряд операций, которые могут быть перегружены таким образом, и все связанные с этим типажи расположены в модуле std::ops. Проверьте эту часть документации для получения полного списка.

Реализация этих типажей следует паттерну. Давайте посмотрим на типаж Add более детально:


# #![allow(unused_variables)]
#fn main() {
# mod foo {
pub trait Add<RHS = Self> {
    type Output;

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

В общей сложности здесь присутствуют три типа: тип impl Add, который мы реализуем, тип RHS, который по умолчанию равен Self и тип Output. Для выражения let z = x + y: x — это тип Self, y — это тип RHS, а z - это тип Self::Output.


# #![allow(unused_variables)]
#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
# 1.0
    }
}
#}

позволит вам сделать следующее:

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!("Площадь s: {}", s.area());
}

Мы просто объявляем тип-параметр T и используем его вместо f64 в определении HasArea и Square. В реализации нужно сделать более хитрые изменения:

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

Чтобы реализовать area, мы должны мочь умножить операнды друг на друга, поэтому мы объявляем T как реализующий std::ops::Mul. Как и Add, Mul принимает параметр Output: т.к. мы знаем, что числа не меняют своего типа, когда их умножают, Output также объявлен как T. T также должен поддерживать копирование, чтобы Rust не пытался переместить self.side в возвращаемое значение.