Bad Rust API design?

If I have a trait like this with a reference to foo it can not be stored in implementation of config

pub trait MyTrait { }

pub trait API {
    fn config(&mut self, foo: &dyn MyTrait);
}

Reference to foo can not be stored in implementation like this:

struct HasTraitObj<'a> {
    foo: &'a dyn MyTrait
}

impl<'a> API for HasTraitObj<'a> {
    fn config(&mut self, foo: &dyn MyTrait) {
        self.foo = foo;
    }
}

I get reference does not outlive borrowed content which is because lifetime of foo is not the same as of self (i.e. 'a, correct?)

A "fix" could be another API design where lifetime is explicit:

pub trait MyTrait { }

pub trait API<'a> {
    fn config(&mut self, foo: &'a dyn MyTrait);
}

And then it can be constrained to be the same:

struct HasTraitObj<'a> {
    foo: &'a dyn MyTrait
}

impl<'a> API<'a> for HasTraitObj<'a> {
    fn config(&mut self, foo: &'a dyn MyTrait) {
        self.foo = foo;
    }
}

Am I correct in my understanding that original API was too restrictive and the proposed "fix" is a Rust way to lift it?

You probably never want to store a temporary borrow (&). If you try, you'll end up with an explosion of lifetime annotations, and the borrow checker will require that foo has been created and already existed before the object that implements HasTraitObj. This makes sense only for temporary views of data, like Iterators.

Use Box<dyn MyTrait> to pass something by reference in a way that allows keeping it (or Arc<dyn Trait> for a shared reference).

3 Likes

@kornel you mean config should look like this?

fn config(&mut self, foo: Box<dyn MyTrait>) {

Yes, and

struct HasTraitObj {
    foo: Box<dyn MyTrait>
}
2 Likes