Design question: Traits, Objects, and Boxes


#1

I am writing an application that stores various data in a content addressable store. I have several implementations (several storage formats), including one that uses plain files, one that uses an SQLite database, and one that just uses an in-memory hash table.

Currently, I represent these with a trait:

pub trait Source {
    fn find(&self, key: &Oid) -> Result<Chunk>;
    fn get_writer<'a>(&'a self) -> Result<Box<Sink + 'a>>;
    fn add(&self, chunk: &Chunk, writer: &Sink) -> Result<()>;
    ...
}
pub trait Sink {
    fn flush(self: Box<Self>) -> Result<()>;
    fn inner(&self) -> &Source;
}

and this is working. However, there are numerous things about it that are awkward:

  1. The ‘Sink’ is clumsy, but I need something to hold the Transaction from rusqlite.
  2. Nothing takes self as &mut. This means every implementation needs inner mutability to actually do anything.
  3. The writer must be boxed.
  4. I’m trying to keep Source object safe, since I’d like to have a function that can look at a disk directory and return the appropriate implementation.
  5. Clients of the Sink need to do weird things like: sink.inner().add(&chunk, sink.inner) to call the add method.

Trying to be object safe restricts me from doing useful things, such as using an associated type within Source to hold the Sink.

Again, I have this working, but I was wondering if anyone had any ideas/suggestions to make this more usable.

I can push the current code up to github if that would be helpful.