Generic over trait with boxed trait object + lifetime

It’s unclear to me why this compiles:

trait A<T: ?Sized>: From<Box<T>> {}

trait B {}

impl A<B> for Foo {}

etc, but not:

trait A<T: ?Sized>: From<Box<for <'a> Fn(&'a Baz) -> Box<T + 'a>>> {}

since T can be a trait in either, but the latter fails to compile with:

error[E0404]: expected trait, found type parameter `T`

Here is a playground with a toy example, which hints at why I would care about this. (It comes up frequently when using the “enum dispatch” pattern, while also needing erasure/dynamic dispatch for certain traits, but needing to avoid lifetimes for those erasures seeping into the enum wrapping types). This will compile if the lines in the PROBLEM LINES block are commented out.

I have deja vu about dealing with boxed generic trait object lifetimes before, but couldn’t find anything here or on SO with several searches.

-> Box<T + 'a>

This wouldn’t work because Box requires a static lifetime.

Box has a static lifetime by default, but does not require it. If you look at the playground, you’ll see other uses of non-static boxed lifetimes that work.

You can’t add a lifetime to a type, so T + 'a is invalid syntax.

I edited you code to make it compile, to make it work I removed all for<'a> ... and replaced them with normal lifetimes, and added a function to help the type checker.

The reason we can’t use for<'a> ... here is because we don’t have where bound for higher rank lifetimes, so we can’t specify that we want all lifetimes 'a such that T outlives 'a, so we must use normal lifetimes.

That being said, is there any reason you are boxing your closures? You shouldn’t ever have to do this normally.

1 Like

Right, but now the lifetime is in the enum, which means that due to the lack of GATs means it propagates out into any types using it as an associated item (somewhat similar to this), even though inference about that lifetime should be entirely local, e.g., “given a reference, I know how to produce a trait object valid for the lifetime of that reference”.

The boxing of the closures was simply an attempt to prevent the lifetime seeping into the outer types. The means of producing the trait object from the reference has nothing to do with the lifetime. The C2Enum is only passing around "Here is a way of producing an A trait object from a Baz", and that way (the closure) is static.

You could use generics

struct C2Stuff<F: for<'a> Fn(&'a Baz) -> Box<A + 'a>>(F);

Then you can Box C2Stuff if needed, this neatly hides the lifetimes.

You can’t be generic over both the lifetime and the type at the same time, which is unfortunate.

I played with it a bit more, and I think this is as generic as I can make it. warning: here there be lifetimes

This problem will have to have lifetimes because you are trying to borrow from the arguments of the closure, and return something owned, so you will have to specify lifetimes.

edit: updated link