内部可变性如何用于缓存行为? [英] How does interior mutability work for caching behavior?

查看:85
本文介绍了内部可变性如何用于缓存行为?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试创建一个使用Pathstruct,并根据需要从指定的路径加载图像.这是我到目前为止的内容:

I'm trying to create a struct that takes a Path and, on demand, loads the image from the path specified. 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()
    } 
}

无法编译:

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

我想我理解为什么,因为cache的生存期与borrow_mut()相关.

I think I understand why because the lifetime of cache is tied to 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.

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

The problem with your current code is that RefCell provides dynamic borrowing semantics. In other words, borrowing the contents of a RefCell is opaque to Rust's borrow checker. 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. 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. Whoops! Memory safety violation.

由于这个原因,从RefCell中借用值与调用borrow_mut()时获得的保护的有效期有关.在这种情况下,保护程序的生存期为get_image的堆栈帧,该函数在函数返回后不再存在.因此,您不能像做的那样借用RefCell的内容.

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(). In this case, the lifetime of the guard is the stack frame of get_image, which no longer exists after the function returns. 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. This enables you to retain cache semantics.

基本思想是返回一个 guard ,其中包含动态图像以及指向它所源自的单元格的指针.完成动态图像处理后,防护装置将被删除,我们可以将图像添加回单元格的缓存中.

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.

为了保持人体工程学,我们将Deref标记为警卫人员,这样您就可以假装它是DynamicImage.这是带有一些注释和其他一些清理内容的代码:

To maintain ergonomics, we impl Deref on the guard so that you can mostly pretend like it is a 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`)

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

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. 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.

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

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. For example, the regex crate uses a simple memory pool to cache compiled regex state. Since this caching should be opaque to callers, it is implemented with interior mutability using precisely the same mechanism outlined here.

这篇关于内部可变性如何用于缓存行为?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆