Перегрузка операторов

Последнее обновление: 06.06.2024

Благодаря системе трейтов Rust поддерживает перегрузку операторов (operator overloading), позволяя определять собственное поведение для таких операторов, как +, -, * и других. Перегрузка операторов позволяет сократить код, сделать его более выразительным.

Для определения операторов надо использовать один из трейтов из модуля std::ops. Поскольку список трейтов довольно большой и может изменяться, то я отмечу лишь некоторые, а полный список можно посмотреть по адресу https://doc.rust-lang.org/std/ops/index.html. В частности, мы можем использовать следующие трейты:

  • Add: оператор сложения +.

  • AddAssign: сложение с присвоением +=.

  • BitAnd: поразрядный оператор &.

  • BitAndAssign: поразрядный оператор с присвоением &=.

  • BitOr: поразрядный оператор |.

  • BitOrAssign: поразрядный оператор с присвоением |=.

  • BitXor: поразрядный оператор ^.

  • BitXorAssign: поразрядный оператор с присвоением ^=.

  • Deref: операция разыменования неизменяемой ссылки типа *v.

  • DerefMut: операция разыменования изменяемой ссылки типа *v = 1;.

  • Div: оператор деления /.

  • DivAssign: деление с присвоением /=.

  • Drop: кастомный код внутри деструктора

  • Fn: оператор вызова функции с передачей неизменяемого объекта

  • FnMut: оператор вызова функции с передачей изменяемого объекта

  • FnOnce: оператор вызова функции с передачей объекта по значению

  • Index: оператор индексации (container[index]) в неизменяемом контексте.

  • IndexMut: оператор индексации (container[index]) в изменяемом контексте.

  • Mul: оператор умножения *.

  • MulAssign: умножение с присвоением *=.

  • Neg: оператор арифметического отрицания -.

  • Not: оператор логического отрицания !.

  • RangeBounds: применяется для определения диапазонов, например, .., a.., ..b, ..=c, d..e, or f..=g.

  • Rem: оператор получения остатка от деления %.

  • RemAssign: остаток от деления с присвоением %=.

  • Shl: оператор сдвига влево <<

  • ShlAssign: сдвиг влево с присвоением <<=.

  • Shr: оператор сдвига вправо >>

  • ShrAssign: сдвиг вправо с присвоением >>=.

  • Sub: оператор вычитания -.

  • SubAssign: вычитание с присвоением -=.

Рассмотрим на примере реализации оператор сложения для кастомной структуры:

use std::ops::Add;  // поключаем трейт Add из модуля std::ops

struct Counter{ 
    value: u32
}

impl Add for Counter{

    type Output = Counter; // тип генерируемого значения
    fn add(self, other: Counter) -> Counter { 
        Counter {
            value: self.value + other.value
        }
    }
}
fn main() {

    let counter1 = Counter{value:5};
    let counter2 = Counter{value:15};
    let counter3 = counter1 + counter2;
    println!("{}", counter3.value);  // 20
}

Прежде всего для использования трейта нам надо подключить этот трейт:

use std::ops::Add; 

Здесь определена структура Counter, которая представляет некоторое число, хранимое в поле value

struct Counter{ 
    value: u32
}

Далее собственно реализуем трейт Add для структуры Counter:

impl Add for Counter{

    type Output = Counter; // тип генерируемого значения
    fn add(self, other: Counter) -> Counter { 
        Counter {
            value: self.value + other.value
        }
    }
}

Большинство трейтов-операторов (но не все!) предполагают реализацию двух компонентов. Прежде всего нам надо определить ассоциированный тип Output, который будет возвращаться третом. В данном случае мы предполагаем, что мы будем складывать два объекта Counter и результатом также будет объект Counter. Поэтому в качестве выходного типа указываем тип Counter:

type Output = Counter;

Далее нам надо определить функцию, которая называется как и трейт, только в нижнем регистре - в нашем случае функцию add. Первый параметр функции - self представляет левый операнд оператора. Второй параметр функции (в примере выше параметр other) представляет правый операнд. Так как мы складываем два значения Counter, то и оба параметра представляют этот тип.

fn add(self, other: Counter) -> Counter { 
    Counter {
        value: self.value + other.value
    }
}

В данном случае просто складываем значения поля value обоих объектов Counter и возвращаем результат в виде нового объекта Counter.

После этого мы сможем складывать два объекта Counter и получать результат сложения в виде нового значения Counter:

let counter1 = Counter{value:5};
let counter2 = Counter{value:15};
let counter3 = counter1 + counter2;

Однако отмечу, что логика реализации оператора может быть произвольной, и возвращаемый типы не обязательно должен представлять тип Counter (тип, для которого реализуется трейт). Например, возвратим просто число:

use std::ops::Add;  // поключаем трейт Add из модуля std::ops

struct Counter{ 
    value: u32
}

impl Add for Counter{

    type Output = u32; // тип генерируемого значения
    fn add(self, other: Counter) -> u32 { 
        self.value + other.value
    }
}
fn main() {

    let counter1 = Counter{value:6};
    let counter2 = Counter{value:15};
    let result = counter1 + counter2;
    println!("{}", result);  // 21
}

Настрока второго операнда

По умолчанию правый - второй операнд представляет значение текущего типа, для которого определяется оператор. Однако мы можем в реальности использовать произвольный тип. Для этого реализация оператора типизируется нужным типом. Например:

use std::ops::Add;  // поключаем трейт Add из модуля std::ops
 
struct Counter{ 
    value: u32
}

// тип правого операнда -  u32
impl Add<u32> for Counter{
 
    type Output = u32; // тип генерируемого значения
    fn add(self, other: u32) -> u32 { 
        self.value + other
    }
}
fn main() {
 
    let counter1 = Counter{value:6};
    let result = counter1 + 11;
    println!("{}", result);  // 17
}

В данном случае тип правого операнда будет представлять тип u32, поэтому и реализация операнда типизируется этим типом:

impl Add<u32> for Counter{
Помощь сайту
Юмани:
410011174743222
Номер карты:
4048415020898850