#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!()
.