Я не совсем уверен, что вам нужна внутренняя изменчивость. Тем не менее, я думаю, решение, которое вы предложили, обычно полезно, поэтому я расскажу об одном способе его достижения.
Проблема с вашим текущим кодом заключается в том, что 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. Поскольку это кэширование должно быть непрозрачным для вызывающих, оно реализуется с внутренней изменчивостью, используя точно такой же механизм, описанный здесь.
Вы уверены, что вам нужна внутренняя изменчивость? Есть ли причина, по которой вы не хотите брать '& mut self'? – BurntSushi5
@ BurntSushi5 Hmmm ... Первоначально я хотел разгрузить кучу обработки изображений в 'threadpool', но' DynamicImage' не является 'Send' или' Sync', так что это за столом. Я думаю, что в этот момент я мог бы взять '& mut self'. У меня эти структуры в «Vec», что я повторяю кучу раз. Могу ли я заимствовать объекты как '& mut' несколько раз из' Vec', если они находятся в разных циклах? –
Мне нужно было бы увидеть код. Наверное, лучше задать новый вопрос. – BurntSushi5