2015-08-18 7 views
3

Я пытаюсь создать 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() 
    } 
} 

Это не может скомпилировать:

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

Есть ли способ структурирования кода, чтобы это сработало?

+1

Вы уверены, что вам нужна внутренняя изменчивость? Есть ли причина, по которой вы не хотите брать '& mut self'? – BurntSushi5

+0

@ BurntSushi5 Hmmm ... Первоначально я хотел разгрузить кучу обработки изображений в 'threadpool', но' DynamicImage' не является 'Send' или' Sync', так что это за столом. Я думаю, что в этот момент я мог бы взять '& mut self'. У меня эти структуры в «Vec», что я повторяю кучу раз. Могу ли я заимствовать объекты как '& mut' несколько раз из' Vec', если они находятся в разных циклах? –

+1

Мне нужно было бы увидеть код. Наверное, лучше задать новый вопрос. – BurntSushi5

ответ

3

Я не совсем уверен, что вам нужна внутренняя изменчивость. Тем не менее, я думаю, решение, которое вы предложили, обычно полезно, поэтому я расскажу об одном способе его достижения.

Проблема с вашим текущим кодом заключается в том, что RefCell обеспечивает динамический заимствования семантики. Другими словами, заимствование содержимого RefCell непрозрачно для рублевого чека. Проблема в том, что когда вы пытаетесь вернуть &DynamicImage, пока он все еще живет внутри RefCell, невозможно, чтобы RefCell отслеживал статус заимствования. Если RefCell разрешил это, то другой код мог бы перезаписать содержимое RefCell, пока был получен кредит из &DynamicImage. Упс! Нарушение безопасности памяти.

По этой причине заимствование значения из RefCell привязано к сроку службы охраны, который вы возвращаете, когда звоните borrow_mut(). В этом случае время жизни охранника представляет собой стек стека get_image, который больше не существует после возвращения функции. Поэтому вы не можете брать содержимое RefCell, как вы делаете.

Альтернативный подход (при сохранении требования внутренней изменчивости) составляет Перемещение значений в и из RefCell. Это позволяет сохранить семантику кэша.

Основная идея - вернуть защитник, который содержит динамическое изображение вместе с указателем обратно в ячейку, из которой он был создан. Как только вы закончите с динамическим изображением, защита будет удалена, и мы сможем добавить изображение обратно в кеш ячейки.

Для обеспечения эргономики, мы нажимаем на охрану так, чтобы вы могли в основном делать вид, что это 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 во второй раз перед первым защита была сброшена, тогда новое изображение будет создано с нуля, так как ячейка будет пустой. Эта семантика трудно изменить (в безопасном коде), потому что вы взяли на себя решение, которое использует внутреннюю изменчивость. Вообще говоря, весь смысл внутренней изменчивости заключается в том, чтобы что-то мутировать, чтобы вызывающий не мог ее наблюдать. Действительно, этот должен быть здесь, предполагая, что открытие изображения всегда возвращает точно такие же данные.

Этот подход может быть обобщен как потокобезопасный (с использованием Mutex для внутренней изменчивости вместо RefCell) и, возможно, более результативным, выбирая другую стратегию кэширования в зависимости от вашего варианта использования. Например, regex crate uses a simple memory pool to cache compiled regex state. Поскольку это кэширование должно быть непрозрачным для вызывающих, оно реализуется с внутренней изменчивостью, используя точно такой же механизм, описанный здесь.

+0

Фантастический! Возможно, вам пригодится модуль «pool», с которым вы связаны. Может быть, достойный его собственный ящик на ящиках.io? –