Перегрузка оператора по значению приводит к использованию перемещенного значения

Компиляция следующего кода Rust, использующего перегрузку операторов

use std::ops::{Add};

#[derive(Show)]
struct Point {
    x: int,
    y: int
}

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 p: Point = Point {x: 1, y: 0};
    let pp = p + p;
}

Приводит к ошибкам компилятора из-за владения p:

<anon>:21:18: 21:19 error: use of moved value: `p`
<anon>:21     let pp = p + p;
                           ^
<anon>:21:14: 21:15 note: `p` moved here because it has type `Point`, which is non-copyable
<anon>:21     let pp = p + p;
                       ^

Обоснование этого объясняется здесь и приводит к RFC, который не был принят (частично по причинам, приведенным в приведенном выше примере). Однако позже следующий RFC по-прежнему вводил сигнатуры по значению для операторов.

Пока я понимаю мотивы решения. Из-за отсутствия у меня опыта работы с ржавчиной я не уверен, каким «правильным» способом будет разрешить приведенный выше код работать (а), если я не хочу копировать, или (б) как сделать структуру копируемой?


person zgerd    schedule 09.01.2015    source источник


Ответы (2)


Если вы не хотите копировать, то, насколько я понимаю новичка, вам нужно реализовать Add для ссылок на Point.

Это будет поддерживаться RFC:

К счастью, выразительность не теряется, так как вы всегда можете реализовать трейт на ссылочных типах. Однако для типов, которые необходимо брать по ссылке, есть небольшая потеря в эргономике, поскольку вам может потребоваться явно заимствовать операнды с помощью &. Положительным моментом является то, что семантика владения становится более понятной: они больше напоминают обычные аргументы функций.

И действительно, это работает:

use std::ops::{Add};

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

impl<'a> Add for &'a Point {
    type Output = Point;

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

fn main() {
    let p: Point = Point {x: 1, y: 0};
    let pp = &p + &p;
    println!("{:?}", pp);
}

(манеж)

Чтобы сделать Point копируемым, просто замените #[derive(Show)] на #[derive(Show,Copy)]. Раньше такие структуры можно было копировать по умолчанию, но это изменено.

person Michał Politowski    schedule 09.01.2015
comment
Проблема в том, что let pp = &p + &p + &p не работает. - person SirVer; 15.01.2015
comment
@SirVer да, вам нужно было бы написать что-то вроде let pp = &(&p + &p) + &p. Я предполагаю, что на практике было бы создать несколько реализаций, как предлагает ответ Владимира Матвеева (или просто вывести Copy и покончить с этим). - person Michał Politowski; 15.01.2015

Если ваша структура не может быть скопирована (например, у нее есть Drop реализация, либо сама по себе, либо для одного из своих полей), то может иметь смысл создать несколько реализаций: значение+значение, значение+ссылка, ссылка+значение и ссылка+ссылка . Первые три могут повторно использовать хранилище одного из операндов, а последний может клонировать один из операндов и потом просто делегировать уже существующие реализации. Таким образом, пользователь вашей библиотеки может легко решить, хотят ли они повторно использовать существующие значения для оптимизации или нет.

На самом деле, вот как, например. BigInt или Complex.

Ваш Point, однако, можно просто сделать Copy, поскольку копировать его дешево.

person Vladimir Matveev    schedule 09.01.2015
comment
Владимир, спасибо за ответ. Что, если мой тип простой, но дорогой для копирования, скажем, тип Matrix1000x1000? Достаточно ли умен компилятор, чтобы избежать копирования в цепных операциях, или мне нужно тривиально писать форварды для 4 упомянутых вами реализаций? - person SirVer; 16.01.2015
comment
Я не уверен, что понимаю ваш вопрос. Если ваш тип дорого копировать, не делайте его Copy и реализуйте трейты операции для четырех вариантов (я+я, &я+я, я+&я, &я+&я). Если у вас такой большой тип, вам все равно придется поместить данные в кучу, поэтому семантика перемещения обеспечит копирование только самой структуры. - person Vladimir Matveev; 16.01.2015