Search code examples
rustassociated-types

How do I obtain a trait object from a wrapper trait object with an associated type?


I have two traits Foo and Bar:

trait Bar {
    fn get_name(&self) -> &str;
}

trait Foo {
    type B: Bar + ?Sized;

    fn get_bar(&self) -> &Self::B;
}

In reality, I'll have many different types of Foos and Bars but each Foo has the associated trait for a type of Bar. Keeping it simple for now, the SimpleFoo is associated with the SimpleBar:

struct SimpleBar {
    name: String,
}

impl Bar for SimpleBar {
    fn get_name(&self) -> &str {
        &self.name
    }
}

struct SimpleFoo {
    bar: Rc<SimpleBar>,
}

impl Foo for SimpleFoo {
    type B = SimpleBar;

    fn get_bar(&self) -> &SimpleBar {
        &self.bar
    }
}

In some places I can use generics and monomorphism, but I need dynamic dispatch in some locations, like this function than needs a dyn Foo whose Bar is a dyn Bar:

fn some_func_that_needs_dyn_foo_returning_a_dyn_bar(foo: &dyn Foo<B = dyn Bar>) {
    // do stuff
}

Since SimpleFoo implements Foo<B = SimpleBar> not Foo<B = dyn Bar> I can't directly pass it (I wish the compiler or a derive or something could do magic here and make this possible), so I have a wrapper class which holds a reference to some specific Foo and can get its specific Bar and make it into a dyn Bar:

struct DynamicFooWrapper<'a, F: Foo> {
    foo: &'a F,
}

impl<'a, F> Foo for DynamicFooWrapper<'a, F>
where
    F: Foo,
    <F as Foo>::B: Sized,
{
    type B = dyn Bar;

    fn get_bar(&self) -> &'a Self::B {
        self.foo.get_bar()
    }
}

fn main() {
    let b = Rc::new(SimpleBar {
        name: "Bar101".to_owned(),
    });
    let f = SimpleFoo { bar: b.clone() };
    some_func_that_needs_dyn_foo_returning_a_dyn_bar(&DynamicFooWrapper { foo: &f })
}

It's unhappy about the return lifetimes is in the implementation of the wrapper:

error[E0310]: the associated type `<F as Foo>::B` may not live long enough
  --> src/main.rs:45:9
   |
45 |         self.foo.get_bar()
   |         ^^^^^^^^^^^^^^^^^^
   |
   = help: consider adding an explicit lifetime bound `<F as Foo>::B: 'static`...
   = note: ...so that the type `<F as Foo>::B` will meet its required lifetime bounds

For more information about this error, try `rustc --explain E0310`.

I don't want to have any static data here. I'd like to tie the lifetime of the &dyn Bar returned here to the lifetime of the foo which the DynamicFooWrapper wraps because that &dyn Bar will live at least as long as the wrapped Foo. For instance, after calling get_bar() on the Foo wrapper, I'd even like to destroy the Foo wrapper and as long as the original Foo item is alive. It should be possible since that guarantees the lifetime of Bar - I'm just not sure how to express this all.


Solution

  • TL/DR: You need to use dyn Bar + 'a instead of plain dyn Bar:

    fn some_func_that_needs_dyn_foo_returning_a_dyn_bar<'a>(_foo: &dyn Foo<B=dyn Bar + 'a>) {
        // do stuff
    }
    
    struct DynamicFooWrapper<'a, F: Foo> {
        foo: &'a F,
    }
    
    impl<'a, F> Foo for DynamicFooWrapper<'a, F>
    where
        F: Foo,
        <F as Foo>::B: Sized,
    {
        type B = dyn Bar + 'a;
    
        fn get_bar(&self) -> &'a Self::B {
            self.foo.get_bar()
        }
    }
    
    fn main() {
        let b = Rc::new(SimpleBar {name: "Bar101".to_owned()});
        let f = SimpleFoo { bar: b.clone() };
        some_func_that_needs_dyn_foo_returning_a_dyn_bar(&DynamicFooWrapper{foo: &f})
    }
    

    Playground

    At some point the dyn Bar + 'a will be matched with some concrete type T. The + 'a constraint tells the compiler that if T contains references, then these references live at least as long as 'a. This is required if you want to take a reference to T with lifetime 'a as in &'a Self::B.