在 Rust 中通过枚举类型实现延迟加载

Implementing a lazy load in by way of enum type in Rust

在我的用例中,我有一个很大的 Layer 类型列表,其中包含图像数据和一些关于图像的元数据:

extern crate image;

type Img = image::ImageBuffer<image::Rgba<u8>, Vec<u8>>;

#[derive(Debug, Clone)]
struct Layer {
    // some metadata fields
    lazy_image: Img,
}

到目前为止一切顺利。但是,我在实际处理图像数据之前执行了一些图层剔除,因此我想以惰性方式执行图像加载,以避免加载我实际上不需要的所有图像。

这是我的第一次尝试:

extern crate image;

type Img = image::ImageBuffer<image::Rgba<u8>, Vec<u8>>;

#[derive(Debug, Clone)]
enum LazyImage {
    Path(String),
    Image(Img)
}

#[derive(Debug, Clone)]
struct Layer {
    // some metadata fields
    lazy_image: LazyImage,
}

impl Layer {
    fn get_image(&mut self) -> Img {
        match self.lazy_image {
            LazyImage::Image(img) => img,
            LazyImage::Path(path) => {
                let img = image::open(path).expect("Could not open image").to_rgba();
                self.lazy_image = LazyImage::Image(img);
                img
            }
        }
    }
}

如您所料,这行不通,因为我在 get_image 中的匹配语句存在所有权问题。编译器建议我借用 self.lazy_image,实际上是在做:

impl Layer {
    fn get_image(&mut self) -> Img {
        match &self.lazy_image {
            LazyImage::Image(img) => img,
            LazyImage::Path(path) => {
                let img = image::open(path).expect("Could not open image").to_rgba();
                self.lazy_image = LazyImage::Image(img);
                img
            }
        }
    }
}

但是,现在我遇到了类型问题:第一个分支(如果图像已加载)returns 引用 &Img 而不是实际的 Img。好的,没问题,让我们去完整的参考。唯一要注意的是,由于我正在对这些图像进行处理,因此它们需要是可变的:

impl Layer {
    fn get_image(&mut self) -> &mut Img {
        match &self.lazy_image {
            LazyImage::Image(img) => img,
            LazyImage::Path(path) => {
                let img = image::open(path).expect("Could not open image").to_rgba();
                self.lazy_image = LazyImage::Image(img);
                &mut img
            }
        }
    }
}

现在它们是同一类型,但可变性不同:在第一个分支中(如果图像已经加载)我得到一个不可变的引用。我尝试了更多的尝试,但未能成功实现我想要的效果。

正如您可能会说的那样,我对 Rust 有点陌生,并且有点不知所措。此外,我实际上并不确定我想要做什么来实现我想要的目标。

任何帮助,无论是告诉我如何满足编译器的要求,还是只是告诉我这一切都错了,我们将不胜感激。

我建议直接在LazyImage类型上实现一个get()方法,因为它感觉加载图像是该类型的内部事务。

由于您很可能不想将图像移出数据结构,因此您应该 return &mut Img。该引用又需要指向存储在 Image 枚举变体中的值。一个枚举变量字段只能通过解构来提取,所以我们需要在加载图像后再次解构。

一种可能的解决方案是使用两个 if let 语句进行解构:

impl LazyImage {
    fn get(&mut self) -> &mut Img {
        if let LazyImage::Path(path) = self {
            let img = image::open(path).expect("Could not open image").to_rgba();
            *self = LazyImage::Image(img);
        }
        if let LazyImage::Image(img) = self {
            img
        } else {
            unreachable!()
        }
    }
}

在第一个 if let 之后,枚举保证是一个 Image。第二次解构也可以使用 match 表达式来完成。编译器会坚持我们包括 else 的情况,因为编译器不能轻易地看到另一个分支是不可达的。

请注意,我们使用 match ergonomics 来避免代码中出现一些噪音。