我正在尝试创建一个struct,Path并按需从指定的路径加载图像。这是我到目前为止的内容:
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()。
无论如何,是否有结构代码以使其起作用?
我并不完全相信您在这里需要内部可变性。但是,我确实认为您提出的解决方案通常很有用,所以我将详细说明实现该问题的一种方法。
当前代码的问题在于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。由于该缓存对于调用者而言应该是不透明的,因此可以使用此处概述的相同机制以内部可变性实现该缓存。