Как реализовать свойство Add для ссылки на структуру?

Я создал двухэлементную структуру Vector, и я хочу перегрузить оператор +.

Я использовал все свои функции и методы для получения ссылок, а не значений, и я хочу, чтобы оператор + работал одинаково.

impl Add for Vector {
    fn add(&self, other: &Vector) -> Vector {
        Vector {
            x: self.x + other.x,
            y: self.y + other.y,
        }
    }
}

В зависимости от того, какое изменение я пытаюсь, я либо получаю проблемы с продолжительностью жизни, либо несовпадения типов. В частности, аргумент &self, похоже, не рассматривается как правильный тип.

Я видел примеры с аргументами шаблона на impl, а также Add, но они просто приводят к различным ошибкам.

Я нашел Как оператор может быть перегружен для разных типов RHS и возвращать значения?, но код в ответе не работает, даже если я положил a use std::ops::Mul; вверху.

Я использую rustc 1.0.0-nightly (ed530d7a3 2015-01-16 22:41:16 +0000)

Я не буду принимать "у вас есть только два поля, зачем использовать ссылку" в качестве ответа; что, если бы я хотел создать 100-элементную структуру? Я приму ответ, который демонстрирует, что даже с большой структурой я должен передавать значение, если это так (я не думаю, что это так). Мне интересно знать хорошее правило для размера структуры и передавая значение vs struct, но это не текущий вопрос.

Ответы

Ответ 1

Вам нужно реализовать Add на &Vector, а не на Vector.

impl<'a, 'b> Add<&'b Vector> for &'a Vector {
    type Output = Vector;

    fn add(self, other: &'b Vector) -> Vector {
        Vector {
            x: self.x + other.x,
            y: self.y + other.y,
        }
    }
}

В своем определении Add::add всегда принимает значение self по значению. Но ссылки являются типами, подобными любым другим 1 поэтому они также могут реализовать черты. Когда черта реализуется в ссылочном типе, тип self является ссылкой; эта ссылка передается по значению. Обычно, передача по значению в Rust подразумевает передачу права собственности, но когда ссылки передаются по значению, они просто копируются (или переустановлены/перемещены, если это изменчивая ссылка), и это не передает права собственности на референт (поскольку ссылка не имеет своего референта в первую очередь). Учитывая все это, для Add::add (и многих других операторов) имеет смысл принимать значение self по значению: если вам нужно взять на себя управление операндами, вы можете реализовать Add в structs/enums напрямую, и если вы нет, вы можете реализовать Add по ссылкам.

Здесь self имеет тип &'a Vector, потому что тот тип, который мы реализуем Add on.

Обратите внимание, что я также указал параметр типа RHS с другим временем жизни, чтобы подчеркнуть тот факт, что времена жизни двух входных параметров не связаны.


1 Собственно, ссылочные типы являются особенными, поскольку вы можете реализовать черты для ссылок на типы, определенные в вашем ящике (т.е. если вам разрешено реализовать черту для T re также разрешено реализовать его для &T). &mut T и Box<T> имеют одно и то же поведение, но это не верно вообще для U<T>, где U не определено в одном ящике.

Ответ 2

Если вы хотите поддерживать все сценарии, вы должны поддерживать все комбинации:

  • & T op U
  • T op & U
  • & T op & U
  • T op U

В самой ржавчине это было сделано через внутренний макрос.

К счастью, есть корзина ржавчины, impl_os, которая также предлагает макрос, чтобы написать этот шаблон для нас: корзина предлагает impl_op_ex! макрос, который генерирует все комбинации.

Вот их образец:

#[macro_use] extern crate impl_ops;
use std::ops;

impl_op_ex!(+ |a: &DonkeyKong, b: &DonkeyKong| -> i32 { a.bananas + b.bananas });

fn main() {
    let total_bananas = &DonkeyKong::new(2) + &DonkeyKong::new(4);
    assert_eq!(6, total_bananas);
    let total_bananas = &DonkeyKong::new(2) + DonkeyKong::new(4);
    assert_eq!(6, total_bananas);
    let total_bananas = DonkeyKong::new(2) + &DonkeyKong::new(4);
    assert_eq!(6, total_bananas);
    let total_bananas = DonkeyKong::new(2) + DonkeyKong::new(4);
    assert_eq!(6, total_bananas);
}

Еще лучше, у них есть impl_op_ex_commutive! это также сгенерирует операторы с обратными параметрами, если ваш оператор оказывается коммутативным.