Копирование / перемещение поля, которое не реализует копирование

#image #rust #move-semantics #deep-copy

#изображение #Ржавчина #перемещение-семантика #глубокое копирование

Вопрос:

У капитана Ферриса есть карта ( seven_seas.png ) области, где он спрятал несколько сокровищ (в координатах (5,7) и (7,9)). Он хочет создать отдельную карту сокровищ для каждого из сокровищ. Исходная карта не должна быть изменена.

Он решает использовать для этого rust и image crate.

 extern crate image;
use crate::image::GenericImage;


struct Desk {
    map_of_seven_seas: image::DynamicImage,
    colored_ink: image::Rgba<u8>,
}

impl Desk {
    fn create_treasure_map(mut self, x: u32, y:u32) -> image::DynamicImage {
        self.map_of_seven_seas.put_pixel(x, y, self.colored_ink);
        self.map_of_seven_seas
    }

}

fn main() {
    let desk = Desk {
        map_of_seven_seas: image::open("seven_seas.png").unwrap(),
        colored_ink: image::Rgba([255, 0, 0, 255]), // red             
    };

    println!("Marking my treasures!");
    // works fine
    let treasure_map_0 = desk.create_treasure_map(5, 7);
    treasure_map_0.save("treasure_map_0.png").unwrap();

    // breaks
    let treasure_map_1 = desk.create_treasure_map(7, 9);
    treasure_map_1.save("treasure_map_1.png").unwrap();
}
 

Темные тучи закрывают небо, когда капитан Феррис понимает, что в его плане есть недостаток:

 error[E0382]: use of moved value: `desk`
  --> src/main.rs:30:26
   |
19 |     let desk = Desk {
   |         ---- move occurs because `desk` has type `Desk`, which does not implement the `Copy` trait
...
26 |     let treasure_map_0 = desk.create_treasure_map(5, 7);
   |                          ---- value moved here
...
30 |     let treasure_map_1 = desk.create_treasure_map(7, 9);
   |                          ^^^^ value used here after move

error: aborting due to previous error

 

Капитан Феррис сейчас в беде. Похоже, он может использовать свои столы только один раз. Он оценивает, на что намекает Mate Cargo, и пытается получить черту копирования для стола (добавление #[derive(Clone, Copy)] к структуре). Но снова он терпит неудачу:

 error[E0204]: the trait `Copy` may not be implemented for this type
 --> src/main.rs:4:17
  |
4 | #[derive(Clone, Copy)]
  |                 ^^^^
5 | struct Desk {
6 |     map_of_seven_seas: image::DynamicImage,
  |     -------------------------------------- this field does not implement `Copy`
  |
  = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
 

image::DynamicImage не может быть скопировано, и он не может получить Copy для него, потому что это часть ящика.

Должен ли капитан Феррис создавать новый стол каждый раз, когда он хочет создать карту сокровищ (включая загрузку map_of_the_seven_seas из файловой системы)? Или есть лучший способ повторно использовать стол?

Для меня это учебное упражнение, поэтому хорошее решение с объяснением было бы лучше, чем умный взлом.

Ответ №1:

Должен ли капитан Феррис создавать новый стол каждый раз, когда он хочет создать карту сокровищ (включая загрузку map_of_the_seven_seas из файловой системы)? Или есть лучший способ повторно использовать стол?

Действительно, есть лучший способ. Проблема здесь заключается в

 fn create_treasure_map(mut self, x: u32, y:u32) -> image::DynamicImage
 

self параметр (независимо mut от того, означает ли он, что вызов этого метода потребляет стол. Хотя извлечение Clone было бы возможно ( Copy определенно нет), оно действительно копировало бы весь рабочий стол каждый раз, что кажется довольно ненужным.

Однако этого можно избежать, если вместо этого использовать метод заимствования self:

 fn create_treasure_map(amp;mut self, x: u32, y:u32) -> amp;image::DynamicImage {
    self.map_of_seven_seas.put_pixel(x, y, self.colored_ink);
    amp;self.map_of_seven_seas
}
 

Однако обратите внимание, что это обновляет значение map_of_seven_seas на месте, то treasure_map_1 есть также будет содержать сокровище, указанное в treasure_map_0 .

Я бы предположил, что это нежелательно, поскольку это было бы слишком просто для владельца второй карты, и скорее победило бы обычную точку карт сокровищ.

Таким образом, исправление заключается в том, чтобы не обновлять стол: только заимствуйте его неизменно (нам не нужно обновлять его или его «справочную» карту семи морей) и скопируйте только карту перед добавлением местоположения teasure:

 fn create_treasure_map(amp;self, x: u32, y:u32) -> image::DynamicImage {
    let mut treasure_map = self.map_of_seven_seas.clone();
    treasure_map.put_pixel(x, y, self.colored_ink);
    treasure_map
}
 

Ответ №2:

Здесь есть много решений:

  • клонировать, а не копировать (просто и легко читается, позволяет одновременно отображать обе карты, просто, я бы посоветовал это, если у вас нет проблем со скамейкой) #[derive(Clone)]
  • заимствовать вместо потребления, но это требует побочного эффекта, не позволяет одновременно отображать обе карты
  • прочитайте еще раз как ваш файл (клонирование намного лучше), разрешите иметь обе карты одновременно

Хорошим решением было бы использовать шаблон защиты:

 use image::{GenericImage, GenericImageView};

struct Desk {
    map_of_seven_seas: image::DynamicImage,
    colored_ink: image::Rgba<u8>,
}

struct PixelTmpChange<'a> {
    image: amp;'a mut image::DynamicImage,
    pixel: image::Rgba<u8>,
    x: u32,
    y: u32,
}

impl PixelTmpChange<'_> {
    fn new(
        image: amp;mut image::DynamicImage,
        x: u32,
        y: u32,
        tmp_pixel: image::Rgba<u8>,
    ) -> PixelTmpChange {
        let pixel = image.get_pixel(x, y);
        image.put_pixel(x, y, tmp_pixel);

        PixelTmpChange { image, pixel, x, y }
    }

    fn get_image(amp;mut self) -> amp;mut image::DynamicImage {
        self.image
    }
}

impl Drop for PixelTmpChange<'_> {
    fn drop(amp;mut self) {
        self.image.put_pixel(self.x, self.y, self.pixel);
    }
}

impl Desk {
    fn create_treasure_map(amp;mut self, x: u32, y: u32, name: amp;str) -> image::error::ImageResult<()> {
        let mut guard = PixelTmpChange::new(amp;mut self.map_of_seven_seas, x, y, self.colored_ink);

        guard.get_image().save(name)
    }
}

fn main() {
    let mut desk = Desk {
        map_of_seven_seas: image::open("seven_seas.png").unwrap(),
        colored_ink: image::Rgba([255, 0, 0, 255]), // red
    };

    println!("Marking my treasures!");
    desk.create_treasure_map(5, 7, "treasure_map_0.png")
        .unwrap();
    desk.create_treasure_map(7, 9, "treasure_map_1.png")
        .unwrap();
}
 

Идея состоит в том, чтобы позволить компилятору выполнить работу по переводу пикселя в исходное состояние. Это можно использовать повторно и чисто. Мы должны использовать время жизни и заимствовать изображение в качестве ссылки mut, поэтому его немного сложно написать, и мы должны попросить охрану получить доступ к изображению. Но в целом, я думаю, что это хорошее решение, если вы не хотите клонировать.

Если вы хотите, вы могли бы create_treasure_map() вместо этого вернуть охрану, чтобы разрешить больше опций, но менее приватное поведение.

Комментарии:

1. Я думаю get_pixel_mut , не реализовано для DynamicImage .

2. @Mihir Мне не очень нравится этот ящик: p

3. Да, вероятно, не лучшая идея просто оставить unimplemented!() .