Generic over trait with boxed trait object + lifetime


#1

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.


#2
-> Box<T + 'a>

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


#3

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.


#4

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.

https://play.rust-lang.org/?version=stable&mode=release&edition=2015&gist=6d881030ac98513f6b5257437a45accf


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.


#5

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.


#6

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.


#7

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

https://play.rust-lang.org/?version=stable&mode=release&edition=2015&gist=11668c735ba827b1c2021c85035a6937


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