What I'm really trying to do is hack around the lack of object-safety for traits with GATs for my one specific trait. If I specialize the impl for each Request<'a> associated type I use it works. And this workaround actually is ok for instantiating the HandlerBox, but I run into the same "impl is not general enough" issue later anyway when blanket implementing the Handler trait for HandlerBox for any given request type.
Thanks for taking a look. I don't think that will work for me since I need to use the lifetime in the Handler trait's method (not shown in the example). Copying my more descriptive write-up from discord:
I have a trait that has an associated type with a generic lifetime (well, a couple) that looks like this:
pub trait AsyncHandlerFifo {
type Request<'a>: Decode<'a>;
type Response: Encode;
type Future<'a>: Future<Output = Self::Response> + 'a where Self: 'a;
fn call<'a>(
&'a mut self,
requester: EndpointAddr,
payload: Self::Request<'a>,
) -> Self::Future<'a>;
And I'm trying to manually create a box type for it so I can have a trait-object despite the no-object-safety-for-GATs-yet restriction. I manually create a vtable with call and drop methods and allocate a re-usable slot on the heap for the returned future (and return as &dyn Future to erase underlying future type without resorting to a fresh Box on every call) - since call mutably borrows self and the returned future captures the borrow, I figure re-using the same allocation for each call should be safe. Anyway I've got it all implemented and it seems to work in a local test but if I try to use it with some generic framework-ey code like:
I get "implementation of AsyncHandlerFifo is not general enough". I guess I'm implicitly hoping the following blanket impl for my custom box type will implement for any lifetime if Req type param takes a lifetime parameter, and that appears to not be the case.
impl<Req, Resp> AsyncHandlerFifo for AsyncHandlerFifoBox<dyn AsyncHandlerFifoObject<Req, Resp>>
where
Req: for<'a> Decode<'a>,
Resp: Encode,
{ ... }
Here I'm using a dummy AsyncHandlerFifoObject trait to attach the request and response types to the custom box without attaching lifetimes to the box. e.g. AsyncHandlerFifoBox<dyn for<'a> AsyncHandlerFifoObject<Request<'a> = &'a str, Response = String>>
Binders don't switch levels; you don't automatically get an implementation for the higher-ranked type (dyn for<'a> Obj<&'a str>) even if you have an implementation that covers the same set of types in a non-higher-ranked fashion (every dyn Obj<T>). You can see this with something like:
trait Ex {}
impl<R> Ex for dyn HandlerObject<R> {}
impl Ex for dyn for<'a> HandlerObject<&'a str> {}
dyn for<'a> HandlerObject<&'a str> is not equal to dyn HandlerObject<R> for any R (after all, what would that R be? for<'a> &'a str? That is not a type, and would give us dyn HandlerObject<for<'a> &'a str>, which has the for in the wrong place).
(Now, the example does come with a scary warning pointing to issue 56105 and saying it will become a hard error. But to the best of my knowledge, that's a bald-faced lie; the current direction is to continue accepting them.[2])
Your situation is a little different in that you're trying to induce some sort of equality as a type parameter, versus as an implementer, but I believe it comes down to the same distinction.
The more typical example is fn(T) vs fn(&T) -- aka for<'any> fn(&'any T). ↩︎
Here's another thread with probably way more discussion about it than you care to read; I'm mostly putting a link in here in case I need the breadcrumbs. ↩︎
Oh wow the shadow trait trick is pretty clever. It looks like in the end we run into the same issue which I guess is that while you can write an impl for any given type
impl<T> HandlerBox<dyn HandlerObject<T>> { }
You cannot write an impl for any given type constructor that takes a lifetime argument:
Except if T<'a> happens to be &'a T, then you can do what you did to make "Box<...> implement the original trait". You can't do that for a custom struct HmmRequest<'a> for example AFAICT (well, for any given struct that takes a single lifetime parameter - you can write an impl for each of the structs individually).
For now what I've done is just written a macro impl AsyncHandlerFifo for AsyncHandlerFifoBox<dyn for<'a> AsyncHandlerFifoObject<$req_ty<'a>, $resp_ty>>, it's not too painful to manually invoke the macro for each request handler type I want to box.