Lifetime on inside trait object stalling despite move

Hi there,

I have a boxed trait object with a function that consumes itself, along with some data.
If the data contains references, and the references do not outlive when the boxed trait object was made, the compiler believes the references have invalid lifetimes.
I would have thought the references only need to be valid across the function call.

This example is simplified, the issue I am having is that the trait objects are created on another thread and sent across a channel, so the data must be created after the trait object would be created.

Example:

struct Data<'a> {
    s: &'a str
}

trait Foo<T> {
    fn foo(self: Box<Self>, t: T);
}

type Bar<'a> = Box<dyn Foo<Data<'a>>>;

fn zog(bar: Bar) {
    let s = String::from("Hello, world!");
    let data = Data {
        s: &s
    };
    
    bar.foo(data); // <-- This consumes bar
}

Playground

Any help would be appreciated.

I'm not sure if this is possible, as I'm not quite sure what the limits and such are for your problem, but I'm guessing that the trait object is created on another thread as you said then passed to zog? Could zog also take a &str (or whatever data is) so you have a lifetime to tie everything together and make the compiler happy. playground

Well if you expand the elided lifetime on zog and insert the type alias, we have

fn zog<'a>(bar: Box<dyn Foo<Data<'a>>>) {

Now, what is the type of t in the call to foo? Well clearly it must be Data<'a>, and the 'a lifetime is a generic parameter, which means the caller of zog chooses it. In fact they could choose 'static if they wanted, requiring your code to be valid even in the case where foo requires a static lifetime.

One approach is to change Bar to

type Bar = Box<dyn for<'a> Foo<Data<'a>>>;

This way the Foo inside the box must accept a Data<'a> for any possible choice of lifetime. This means that, unlike with the generic lifetime, you pick what the lifetime 'a is, not the caller. For example, you can use the lifetime of s as seen here.

1 Like

Does one of these help?

struct Data {
    s: String
}

trait Foo<T> {
    fn foo(self: Box<Self>, t: T);
}

type Bar = Box<dyn Foo<Data>>;

fn zog(bar: Bar) {
    let s = String::from("Hello, world!");
    let data = Data {
        s
    };
    
    bar.foo(data);
}

or

struct Data<'a> {
    s: &'a str
}

trait Foo<T> {
    fn foo(self: Box<Self>, t: T);
}

type Bar<'a> = Box<dyn Foo<Data<'a>>>;

fn zog(bar: Bar) {
    let s = "Hello, world!";
    let data = Data {
        s
    };
    
    bar.foo(data);
}

Both compile, but don't actually answer your question.

Thanks @alice, I'm realising this can extend to bounds' predicates as well, especially where closures are inputs. (Example F: for<'a> Fn(&'a str) )