How can I store a thunk that takes a trait object argument?


#1

I’ve been banging my head about lifetime errors for a few days in Servo, and I’ve reduced my problem to a 100 line example. You can see it at https://gist.github.com/jdm/6d0eda6dbdcbbb13c799 along with the errors I’m receiving.

My problem is this - I have two threads (A and B), and an object O which can only be used on thread B. I want to send a wrapper W for the object to thread A, which will invoke a trait method M on it, passing a closure argument C. This closure C accepts a reference to a trait object; this trait is implemented by the original object O in thread A. Now, this trait method M should cause a message to be sent to thread B which contains the wrapper object W and the closure C. Upon receiving this message, thread A will unwrap the wrapper W, and invoke the closure C, passing the reference to the trait object of O as an argument. With me so far?

The problem here is that in order to pass FnOnce values around between the threads for the closure, I need to pack it up in a Thunk. When this is stored in a struct for sending, the trait object in the thunk’s arguments requires a lifetime bound. That means it’s either &'a (TraitObject+'a) (or something else like 'static). However, the 'a that gets inferred when I create the struct is much too large, so that when I later unwrap the wrapper object (which is an Arc<Mutex<Whatever>> in this case), I get a lifetime mismatch.

As far as I can tell this is the problem that higher-rank trait bounds were set up to solve, but I’m stuck because I’m storing this closure type in a struct field, rather than just using it in function arguments. Additionally, any attempt to use for<'a> Thunk<(...)> fails because Thunk isn’t a trait.

Am I missing something? I’ve tried using Box<TraitObject+'static> instead of &'a (TraitObject+'a), but the unwrapped value still doesn’t fulfill the 'static lifetime requirement.


#2

cc @nikomatsakis. This is similar to HRTB and hopefully we can accommodate this.


#3

I had a similar problem and luckily it got solved with the help with SO. I don’t think you can use Thunk as all. The type is just defined badly for this purpose. I should not expose a lifetime.

The solution is to use Invoke instead and make a new wrapper struct.

You can see it here:


#4

Thank for that! It looks like the right solution, but in Servo when the implementor of AsyncResponseListener is in a different crate than the definition, I get a source trait is private error when I try to access the inner value of the Listener tuple struct. That’s pretty frustrating.


#5

I worked around the previous error by implementing a public invoke method on the wrapper structure in the original crate. Woohoo!


#6

Good it worked for you. :slight_smile: And for some reason, when you also define a proper factory function, it helps such that you don’t have to annotate the type for the closure arguments any more. Dunno why.

impl Listener {
    fn new<F: FnOnce(&AsyncResponseListener) + Send + 'static>(f: F) -> Listener {  
        Listener(box f)
    }
}