【问题标题】:Copy/Move an field that doesn't implement Copy复制/移动未实现复制的字段
【发布时间】:2020-12-01 08:39:51
【问题描述】:

费里斯船长有一张地图 (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 所暗示的内容,并尝试推导出 Desk 的 Copy 特征(将 #[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,因为它是 crate 的一部分。

费里斯船长每次想要创建藏宝图(包括从文件系统加载map_of_the_seven_seas)时都必须创建一个新的Desk吗?或者有没有更好的方法来重复使用 Desk?

这对我来说是一个学习练习,所以一个带有解释的好解决方案会比一个聪明的 hack 更好。

【问题讨论】:

    标签: image rust move-semantics deep-copy


    【解决方案1】:

    费里斯船长每次想要创建藏宝图(包括从文件系统加载map_of_the_seven_seas)时都必须创建一个新的Desk吗?或者有没有更好的方法来重复使用 Desk?

    确实有更好的方法。这里的问题是

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

    self 参数(无论是否mut)表示调用此方法消耗桌子。虽然派生 Clone 是可能的(Copy 肯定不是),但它确实会每次都复制整个办公桌,这似乎相当不必要。

    不过,这可以通过使用 borrow self 方法来避免:

    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
    }
    

    但是请注意,这会在原地更新map_of_seven_seas,这意味着treasure_map_1包含treasure_map_0 中提到的宝藏。

    我认为这是不可取的,因为这对于第二张地图的持有者来说有点太容易了,并且宁愿打败藏宝图的通常点。

    因此,解决方法是不更新桌子:仅以不可变的方式借用它(我们不需要更新它或其“参考”七大海地图)并在添加茶点位置之前仅复制地图:

    fn create_treasure_map(&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: &'a mut image::DynamicImage,
          pixel: image::Rgba<u8>,
          x: u32,
          y: u32,
      }
      
      impl PixelTmpChange<'_> {
          fn new(
              image: &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(&mut self) -> &mut image::DynamicImage {
              self.image
          }
      }
      
      impl Drop for PixelTmpChange<'_> {
          fn drop(&mut self) {
              self.image.put_pixel(self.x, self.y, self.pixel);
          }
      }
      
      impl Desk {
          fn create_treasure_map(&mut self, x: u32, y: u32, name: &str) -> image::error::ImageResult<()> {
              let mut guard = PixelTmpChange::new(&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() 返回守卫,以允许更多的选择但更少的私人行为。

      【讨论】:

      • 我猜get_pixel_mut 没有为DynamicImage 实现。
      • @Mihir 我不太喜欢这个箱子:p
      • 是的,留下unimplemented!() 可能不是最好的主意。
      猜你喜欢
      • 1970-01-01
      • 2019-06-07
      • 2018-02-15
      • 2012-01-08
      • 2014-03-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多