Как реализовать свойство 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! это также сгенерирует операторы с обратными параметрами, если ваш оператор оказывается коммутативным.