我不完全相信你需要内部可变性。但是,我确实认为您提出的解决方案是一般来说很有用,所以我将详细说明实现它的一种方法。
您当前代码的问题是RefCell
提供dynamic借用语义。换句话说,借用 a 的内容RefCell
对于 Rust 的借用检查器来说是不透明的。问题是,当你尝试返回一个&DynamicImage
当它仍然存在于RefCell
,这是不可能的RefCell
跟踪其借贷状态。如果一个RefCell
允许这种情况发生,那么其他代码可能会覆盖该内容RefCell
虽然有一笔贷款&DynamicImage
。哎呀!内存安全违规。
因此,从 a 中借用一个值RefCell
与你调用时返回的警卫的生命周期相关borrow_mut()
。在这种情况下,守卫的生命周期是堆栈帧get_image
,在函数返回后不再存在。因此,您不能借用RefCell
就像你正在做的那样。
另一种方法(同时保持内部可变性的要求)是move内和外的值RefCell
。这使您能够保留缓存语义。
基本思想是返回一个guard它包含动态图像以及返回其来源单元格的指针。处理完动态图像后,防护措施将被删除,我们可以将图像添加回单元的缓存中。
为了保持人体工程学,我们意味着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`)
这里有一个非常重要的警告需要注意:这只有一个缓存位置,这意味着如果您调用get_image
在第一个守卫被放下之前第二次,由于单元格为空,因此将从头开始生成新图像。这种语义很难改变(在安全代码中),因为您已经致力于使用内部可变性的解决方案。一般来说,内部可变性的全部要点是在调用者无法观察到的情况下改变某些东西。确实,那should这里的情况就是这样,假设打开图像总是返回完全相同的数据。
这种方法可以概括为线程安全的(通过使用Mutex
对于内部可变性而不是RefCell
),并且根据您的用例选择不同的缓存策略可能会提高性能。例如,regexcrate 使用一个简单的内存池来缓存已编译的正则表达式状态。由于此缓存对调用者来说应该是不透明的,因此它是使用与此处概述的完全相同的机制通过内部可变性来实现的。