内部可变性如何用于缓存行为?

Wes*_*ser 5 mutability rust

我正在尝试创建一个structPath并按需从指定的路径加载图像。这是我到目前为止的内容:

extern crate image;

use std::cell::{RefCell};
use std::path::{Path};
use image::{DynamicImage};

pub struct ImageCell<'a> {
    image: RefCell<Option<DynamicImage>>,
    image_path: &'a Path, 
}

impl<'a> ImageCell<'a> {
    pub fn new<P: AsRef<Path>>(image_path: &'a P) -> ImageCell<'a>{
        ImageCell { image: RefCell::new(None), image_path: image_path.as_ref() }
    }

    //copied from https://doc.rust-lang.org/nightly/std/cell/index.html#implementation-details-of-logically-immutable-methods
    pub fn get_image(&self) -> &DynamicImage {
        {
            let mut cache = self.image.borrow_mut();
            if cache.is_some() {
                return cache.as_ref().unwrap(); //Error here
            }

            let image = image::open(self.image_path).unwrap();
            *cache = Some(image);
        }

        self.get_image()
    } 
}
Run Code Online (Sandbox Code Playgroud)

无法编译:

src/image_generation.rs:34:24: 34:29 error: `cache` does not live long enough
src/image_generation.rs:34                 return cache.as_ref().unwrap();
                                                  ^~~~~
src/image_generation.rs:30:46: 42:6 note: reference must be valid for the anonymous lifetime #1 defined on the block at 30:45...
src/image_generation.rs:30     pub fn get_image(&self) -> &DynamicImage {
src/image_generation.rs:31         {
src/image_generation.rs:32             let mut cache = self.image.borrow_mut();
src/image_generation.rs:33             if cache.is_some() {
src/image_generation.rs:34                 return cache.as_ref().unwrap();
src/image_generation.rs:35             }
                           ...
src/image_generation.rs:32:53: 39:10 note: ...but borrowed value is only valid for the block suffix following statement 0 at 32:52
src/image_generation.rs:32             let mut cache = self.image.borrow_mut();
src/image_generation.rs:33             if cache.is_some() {
src/image_generation.rs:34                 return cache.as_ref().unwrap();
src/image_generation.rs:35             }
src/image_generation.rs:36 
src/image_generation.rs:37             let image = image::open(self.image_path).unwrap();
                           ...
Run Code Online (Sandbox Code Playgroud)

我想我理解为什么,因为生命周期cache与之息息相关borrow_mut()

无论如何,是否有结构代码以使其起作用?

Bur*_*hi5 5

我并不完全相信您在这里需要内部可变性。但是,我确实认为您提出的解决方案通常很有用,所以我将详细说明实现该问题的一种方法。

当前代码的问题在于RefCell提供了动态借用语义。换句话说,借用a的内容RefCell对于Rust的借用检查器是不透明的。问题是,当您尝试退还&DynamicImage仍在内的时RefCell,则无法RefCell跟踪其借用状态。如果RefCell允许发生这种情况,那么其他代码可以覆盖RefCell借出的那一刻的内容&DynamicImage。哎呀!违反内存安全性。

因此,从 RefCell从中与致电时获得的后卫的寿命有关borrow_mut()。在这种情况下,保护的生存期是的堆栈框架get_image,在函数返回后,该框架不再存在。因此,您不能借用RefCell您正在执行的内容。

另一种方法(在保持内部可变性要求的同时)是 移入和移出RefCell。这使您可以保留高速缓存语义。

基本思路是返回一个 包含动态图像保护措施,以及一个指向其起源单元的指针。一旦完成了动态图像处理,防护装置将被删除,我们可以将图像添加回单元格的缓存中。

为了保持人体工程学,我们会安排Deref警卫人员,这样您就可以假装自己DynamicImage。这是带有一些注释和一些其他清理内容的代码:

use std::cell::RefCell;
use std::io;
use std::mem;
use std::ops::Deref;
use std::path::{Path, PathBuf};

struct ImageCell {
    image: RefCell<Option<DynamicImage>>,
    // Suffer the one time allocation into a `PathBuf` to avoid dealing
    // with the lifetime.
    image_path: PathBuf,
}

impl ImageCell {
    fn new<P: Into<PathBuf>>(image_path: P) -> ImageCell {
        ImageCell {
            image: RefCell::new(None),
            image_path: image_path.into(),
        }
    }

    fn get_image(&self) -> io::Result<DynamicImageGuard> {
        // `take` transfers ownership out from the `Option` inside the
        // `RefCell`. If there was no value there, then generate an image
        // and return it. Otherwise, move the value out of the `RefCell`
        // and return it.
        let image = match self.image.borrow_mut().take() {
            None => {
                println!("Opening new image: {:?}", self.image_path);
                try!(DynamicImage::open(&self.image_path))
            }
            Some(img) => {
                println!("Retrieving image from cache: {:?}", self.image_path);
                img
            }
        };
        // The guard provides the `DynamicImage` and a pointer back to
        // `ImageCell`. When it's dropped, the `DynamicImage` is added
        // back to the cache automatically.
        Ok(DynamicImageGuard { image_cell: self, image: image })
    }
}

struct DynamicImageGuard<'a> {
    image_cell: &'a ImageCell,
    image: DynamicImage,
}

impl<'a> Drop for DynamicImageGuard<'a> {
    fn drop(&mut self) {
        // When a `DynamicImageGuard` goes out of scope, this method is
        // called. We move the `DynamicImage` out of its current location
        // and put it back into the `RefCell` cache.
        println!("Adding image to cache: {:?}", self.image_cell.image_path);
        let image = mem::replace(&mut self.image, DynamicImage::empty());
        *self.image_cell.image.borrow_mut() = Some(image);
    }
}

impl<'a> Deref for DynamicImageGuard<'a> {
    type Target = DynamicImage;

    fn deref(&self) -> &DynamicImage {
        // This increases the ergnomics of a `DynamicImageGuard`. Because
        // of this impl, most uses of `DynamicImageGuard` can be as if
        // it were just a `&DynamicImage`.
        &self.image
    }
}

// A dummy image type.
struct DynamicImage {
    data: Vec<u8>,
}

// Dummy image methods.
impl DynamicImage {
    fn open<P: AsRef<Path>>(_p: P) -> io::Result<DynamicImage> {
        // Open image on file system here.
        Ok(DynamicImage { data: vec![] })
    }

    fn empty() -> DynamicImage {
        DynamicImage { data: vec![] }
    }
}

fn main() {
    let cell = ImageCell::new("foo");
    {
        let img = cell.get_image().unwrap(); // opens new image
        println!("image data: {:?}", img.data);
    } // adds image to cache (on drop of `img`)
    let img = cell.get_image().unwrap(); // retrieves image from cache
    println!("image data: {:?}", img.data);
} // adds image back to cache (on drop of `img`)
Run Code Online (Sandbox Code Playgroud)

这里有一个非常重要的警告要注意:这仅具有一个缓存位置,这意味着如果您get_image在删除第一个警戒之前第二次调用,则由于该单元格为空,因此将从头开始生成新图像。这种语义很难更改(使用安全代码),因为您已承诺使用内部可变性的解决方案。一般而言,内部可变性的全部要点是使呼叫者无法观察到的东西发生突变。事实上,这应该是这里的情况,假设总是打开图像精确返回相同的数据。

可以将这种方法推广为线程安全的(通过Mutex用于内部可变性而不是RefCell),并可以根据您的用例选择不同的缓存策略来提高性能。例如,regex板条箱使用简单的内存池来缓存已编译的regex state。由于该缓存对于调用者而言应该是不透明的,因此可以使用此处概述的相同机制以内部可变性实现该缓存。