9

を取得し、オンデマンドで、指定されたパスからイメージをロードする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()

これが機能するようにコードを構造化する方法はありますか?

4

1 に答える 1

9

ここで内部の可変性が必要であるとは完全には確信していません。ただし、提案された解決策は一般的に役立つと思いますので、それを達成するための 1 つの方法について詳しく説明します。

現在のコードの問題は、動的借用セマンティクスをRefCell提供することです。つまり、 a の内容を借用することは、Rust の借用チェッカーには不透明です。問題は、が 内にまだ存在しているときに を返そうとすると、がその借用ステータスを追跡できないことです。がそれを許可した場合、からの貸し出し中に、他のコードが の内容を上書きする可能性があります。おっと!メモリの安全性違反。RefCell&DynamicImageRefCellRefCellRefCellRefCell&DynamicImage

このため、 a から値を借りることは、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`)

ここで注意すべき非常に重要な注意点があります。これにはキャッシュの場所が 1 つしかありません。つまりget_image、最初のガードがドロップされる前にもう一度呼び出すと、セルが空になるため、新しい画像が最初から生成されます。内部可変性を使用するソリューションに取り組んでいるため、このセマンティックを (安全なコードで) 変更することは困難です。一般的に言えば、内部の可変性の全体的なポイントは、呼び出し元がそれを観察できないように何かを変更することです。実際、画像を開くと常に正確に同じデータが返されると仮定すると、ここではそうなるはずです。

Mutexこのアプローチは、( の代わりに for internal mutabilityを使用することにより) スレッド セーフになるように一般化できRefCell、ユース ケースに応じて異なるキャッシュ戦略を選択することで、パフォーマンスを向上させることができます。たとえば、regexクレートは単純なメモリ プールを使用して、コンパイルされた正規表現の状態をキャッシュします。このキャッシングは呼び出し元に対して不透明であるべきであるため、ここで概説したのとまったく同じメカニズムを使用して、内部可変性で実装されます。

于 2015-08-18T02:54:24.483 に答える