简体   繁体   English

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

[英]How does interior mutability work for caching behavior?

I'm trying to create a struct that takes a Path and, on demand, loads the image from the path specified. 我正在尝试创建一个采用Pathstruct ,并根据需要从指定的路径加载图像。 Here's what I have so far: 这是我到目前为止的内容:

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()
    } 
}

This fails to compile: 无法编译:

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();
                           ...

I think I understand why because the lifetime of cache is tied to borrow_mut() . 我想我理解为什么,因为cache的生存期与borrow_mut()

Is there anyway to structure the code so that this works? 无论如何,是否有结构代码以使其起作用?

I'm not totally convinced you need interior mutability here. 我并不完全相信您在这里需要内部可变性。 However, I do think the solution you've proposed is generally useful, so I'll elaborate on one way to achieve it. 但是,我确实认为您提出的解决方案通常很有用,所以我将详细说明实现该问题的一种方法。

The problem with your current code is that RefCell provides dynamic borrowing semantics. 当前代码的问题是RefCell提供了动态借用语义。 In other words, borrowing the contents of a RefCell is opaque to Rust's borrow checker. 换句话说,借用RefCell的内容对于Rust的借用检查器是不透明的。 The problem is, when you try to return a &DynamicImage while it still lives inside the RefCell , it is impossible for the RefCell to track its borrowing status. 问题是,当你试图返回一个&DynamicImage ,而它仍然生活在里面RefCell ,这是不可能的RefCell跟踪其借贷的地位。 If a RefCell allowed that to happen, then other code could overwrite the contents of the RefCell while there was a loan out of &DynamicImage . 如果RefCell允许发生这种情况,那么当&DynamicImage借出资金时,其他代码可能会覆盖RefCell的内容。 Whoops! 哎呀! Memory safety violation. 违反内存安全性。

For this reason, borrowing a value out of a RefCell is tied to the lifetime of the guard you get back when you call borrow_mut() . 因此,从RefCell借用值与您在调用borrow_mut()时获得的保护的寿命有关。 In this case, the lifetime of the guard is the stack frame of get_image , which no longer exists after the function returns. 在这种情况下,保护的生存期为get_image的堆栈帧,该函数在函数返回后不再存在。 Therefore, you cannot borrow the contents of a RefCell like you're doing. 因此,您不能像这样做一样借用RefCell的内容。

An alternative approach (while maintaining the requirement of interior mutability) is to move values in and out of the RefCell . 另一种方法(在保持内部可变性要求的同时)是移入和移出RefCell This enables you to retain cache semantics. 这使您可以保留高速缓存语义。

The basic idea is to return a guard that contains the dynamic image along with a pointer back to the cell it originated from. 基本思想是返回一个包含动态图像的保护措施 ,以及一个指向其起源单元的指针。 Once you're done with the dynamic image, the guard will be dropped and we can add the image back to the cell's cache. 一旦完成了动态图像处理,防护装置将被删除,我们可以将图像添加回单元格的缓存中。

To maintain ergonomics, we impl Deref on the guard so that you can mostly pretend like it is a DynamicImage . 为了保持人体工程学,我们将Deref在警卫上,这样您就可以假装它是DynamicImage Here's the code with some comments and a few other things cleaned up: 这是带有一些注释和一些其他清理内容的代码:

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`)

There is a really important caveat to note here: This only has one cache location, which means if you call get_image a second time before the first guard has been dropped, then a new image will be generated from scratch since the cell will be empty. 这里有一个非常重要的警告要注意:这仅具有一个缓存位置,这意味着如果在删除第一个防护之前第二次调用get_image ,则由于该单元格为空,因此将从头开始生成新的图像。 This semantic is hard to change (in safe code) because you've committed to a solution that uses interior mutability. 这种语义很难更改(使用安全代码),因为您已承诺使用内部可变性的解决方案。 Generally speaking, the whole point of interior mutability is to mutate something without the caller being able to observe it. 一般而言,内部可变性的全部要点是使呼叫者无法观察到的东西发生突变。 Indeed, that should be the case here, assuming that opening an image always returns precisely the same data. 事实上,这应该是这里的情况,假设总是打开图像精确返回相同的数据。

This approach can be generalized to be thread safe (by using Mutex for interior mutability instead of RefCell ) and possibly more performant by choosing a different caching strategy depending on your use case. 可以将这种方法推广为线程安全的(通过使用Mutex来实现内部可变性而不是RefCell ),并且可以根据您的用例选择不同的缓存策略来提高性能。 For example, the regex crate uses a simple memory pool to cache compiled regex state . 例如, regex板条箱使用一个简单的内存池来缓存已编译的正则表达式状态 Since this caching should be opaque to callers, it is implemented with interior mutability using precisely the same mechanism outlined here. 由于该缓存对于调用者而言应该是不透明的,因此可以使用此处概述的相同机制以内部可变性实现该缓存。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM