A Factory with and without a Cache wrapper, with the same trait API?


#1

I’m trying implement a cache that wraps a factory for some data that is time-consuming to produce on-demand, but there are use cases for allowing an application to decide whether or not to use the cache. For example, it would even be nice to write a generic method that could be tested on the direct implementation and the cache version to compare performance.

So far what I’ve got is something like:

trait Resource: Read {}

trait Factory {
    type R: Resource;
    /// time-consuming system resource allocation
    fn get(&self, index: u8) -> Result<Self::R, ()>;
}

struct CacheEntry<R: Resource> {
    value: Arc<Mutex<R>>,
}

struct Cache<F: Factory> {
    factory: F,
    map: RefCell<BTreeMap<u8, CacheEntry<F::R>>>,
}

impl<R: Resource> Resource for CacheEntry<R> {}

impl<R: Resource> Read for CacheEntry<R> {
    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
        self.value.lock().unwrap().read(buf)
    }
}

impl<F: Factory> Factory for Cache<F> {
    type R = CacheEntry<F::R>;

    fn get(&self, index: u8) -> Result<Self::R, ()> {
        let mut map = self.map.borrow_mut();

        match map.entry(index) {
            Entry::Occupied(e) => Ok(CacheEntry { value: e.get().value.clone() }),
            Entry::Vacant(v) => {
                let resource = self.factory.get(index)?;
                let entry = CacheEntry { value: Arc::new(Mutex::new(resource)) };

                Ok(CacheEntry { value: v.insert(entry).value.clone() })
            }
        }
    }
}

With some sample impls:

/// Some raw pointers and manually created slices to simulate something like mmapped memory
/// from a C API
struct BufferResource<'a> {
    slice: &'a [u8],
    raw: *const u8,
}

impl<'a> Resource for BufferResource<'a> {}

impl<'a> Read for BufferResource<'a> {
    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
        let mut reader = BufReader::new(self.slice);
        reader.read(buf)
    }
}

impl<'a> Deref for BufferResource<'a> {
    type Target = [u8];

    fn deref(&self) -> &[u8] {
        self.slice
    }
}

This works, but ideally I’d like to be able to implement Deref for the CachedEntry (and add it to the constraints for Resource) so that it can be used directly in calls to things like Write::write without an intermediate Read::read into a temporary buffer. Is there a more idiomatic way to define the traits or a rustier way to think about the problem in general?

Full gist: