Trait objects and other Rust features, looking for advice

#1

I’m working on a library/program https://github.com/d3zd3z/rsure. Right now, I have a trait Store that has three implementations. The main program uses a trait object so that the particular store type used can be determined by the command line.

However, I’m working on something that seems like it wants a bit more advanced features, and am having a difficult time keeping this as a trait object.

What’d I’d like to add is a method to my trait:

pub trait Store {
    ...
    fn make_temp(&self) -> Result<Box<dyn TempFile>>;
}

where this new TempFile would be something like:

pub trait TempFile: Write {
    fn commit(self, ...) -> Result<()>;
}

In addition, it would usually implement Drop so that it could cleanup the temp file if it wasn’t committed.

This somewhat describes what I want, but there is nothing tying the lifetime of a given TempFile back to a particular Store, so I can’t have a reference back to it. I could require the Store be given as an argument to commit, but I’d have to use something like Any to be able to get back the actual concrete type, and I’m not sure how to make sure the user really calls it with the right argument.

I tried adding lifetimes to the TempFile trait, but I didn’t get very far with that.

Any suggestions on how to best represent this in Rust. One option would be to get rid of the trait, and just directly use one of the concrete implementations. This would be easy to implement there, as the TempFile would just have a lifetime and contain its own reference back to the parent Store. There is pretty much only one active implementation, but it would be nice to be able to keep it abstract so that could support something like a remote Store.

Thanks,
David

#2

BTW, there is a lot of similarity between what I’m trying to do and the Transaction in rusqlite.

#3

I think you can do something like this:

fn make_temp<'a>(&'a self) -> Result<Box<dyn TempFile + 'a>>;

Or to the same effect:

fn make_temp(&self) -> Result<Box<dyn TempFile + '_>>;

Then your TempFile impl can contain a reference back to the concrete store.

#4

I tried adding the lifetime, but I don’t seem to be able to make a struct that has a lifetime implement that trait:

    fn make_temp(&self) -> Result<Box<dyn TempFile + '_>>;

and then:

impl<'a> TempFile for WeaveTemp<'a> {
}
error[E0478]: lifetime bound not satisfied
   --> src/store/weave.rs:141:10
    |
141 | impl<'a> TempFile for WeaveTemp<'a> {
    |          ^^^^^^^^
    |
note: lifetime parameter instantiated with the lifetime 'a as defined on the impl at 141:6
   --> src/store/weave.rs:141:6
    |
141 | impl<'a> TempFile for WeaveTemp<'a> {
    |      ^^
    = note: but lifetime parameter must outlive the static lifetime
#5

If I add the lifetime explicitly to the TempFile trait:

pub trait TempFile<'a>: Write + Any {}

and adjust the implementation, I get effectively the same error:

impl<'a> TempFile<'a> for WeaveTemp<'a> {
}
error[E0478]: lifetime bound not satisfied
   --> src/store/weave.rs:141:10
    |
141 | impl<'a> TempFile<'a> for WeaveTemp<'a> {
    |          ^^^^^^^^^^^^
    |
note: lifetime parameter instantiated with the lifetime 'a as defined on the impl at 141:6
   --> src/store/weave.rs:141:6
    |
141 | impl<'a> TempFile<'a> for WeaveTemp<'a> {
    |      ^^
    = note: but lifetime parameter must outlive the static lifetime

This makes sense to me, but I’m not sure what to do about it.

#6

What does the rest of your code look like?

This is an example I came up with just now, but I guess you must have something different, namely something requires 'static.

1 Like
#7

Ah, thank you, I found the difference, It was the + Any constraint that I had on TempFile. That was an attempt to do this without the lifetimes. But, Any has a 'static constraint, which is where that was coming from. It seems to be working now.

#8

Ah yes, I didn’t even notice the Any in your last post :man_facepalming: