I have a simple type with a generic parameter, and I want to convert its insides from a concrete type to a boxed dyn trait for more internal flexibility.
Along the line of turning this:
struct MyStruct<T> {
vec: Box<Vec<T>> //I know Box<Vec<_>> is redundant
}
impl<T> MyStruct<T> {
fn new(vec: Box<Vec<T>>) {
Self{vec};
}
}
fn main() {
let v = 42;
let my_struct = MyStruct::<&u32>::new(Box::new(vec![&v]));
}
into this
struct MyStruct<T> {
vec: Box<dyn VecTrait<T>>
}
impl<T> MyStruct<T> {
fn new(vec: Box<dyn VecTrait<T> + '_>) {
Self{vec};
}
}
trait VecTrait<T> {}
impl<T> VecTrait<T> for Vec<T> {}
fn main() {
let v = 42;
let my_struct = MyStruct::<&u32>::new(Box::new(vec![&v]));
}
But somehow I need to declare that dyn VecTrait<T> should be bound by the same implicit lifetime as T, and do that with a minimum of explicit lifetime annotation spraying out across the rest of the code.
Is this possible? Or am I thinking about this wrong?
I’ve occasionally wanted a way to refer to the maximum lifetime of a particular type for situations exactly like this, where you want your type to behave exactly like it owns a T and can therefore can be kept as long as a T can. Unfortunately, I haven’t seen any proposals for this get to the RFC stage— Either there’s some implementation difficulty or the need is too niche for the language designers to be interested, it seems.
(Or maybe I’m just not paying enough attention to the RFC stream…)
TL;DR: You can't avoid a lifetime parameter if you want to be able to coerce non-'staticVec<T> to some dyn VecTrait<T> + 't in your data structure (but you can make T: 't implied).
It's already the case that MyStruct<T>: 'x if and only if T: 'x, so "the lifetime of MyStruct<T> corresponds to MyStruct<T> owning a T" is already true. So that must not be the problem.
A dyn Trait is really a dyn Trait + 'd, where
Impl: Trait can coerce to dyn Trait + 'd if only if Impl: 'd
(I think this is your actual problem, connecting 'd to T)
Box<dyn Trait> outside of function bodies means Box<dyn Trait + 'static>
So if you want to support cases other than Impl: 'static, you need a lifetime parameter on MyStruct<..>
Lifetime bounds[1] on the parameters of your nominal type definition are implied bounds elsewhere, so -- while that lifetime is going to show up all over the place -- you don't have to repeat the bound
But this is usually more a technique to smuggle an upper bound into the lifetime of higher ranked types than to infer an outlives bound on nominal type definitions
Thank you for explaining all that! Thanks also for your amazing explanations on Learning Rust.
This situation does feel a bit like <...need a metaphor...> That is, each step / puzzle piece makes sense and is correct in its implications, but the destination / conclusion feels wrong. (I know it's logically right, but I say wrong with respect to a "higher" idea about what one would want from the language)
Specifically, in my case, the trait is an implementation detail that should never escape the interface (The MyStruct) But changing the implementation forces a superfluous change to the interface.
I am interpreting what you wrote to generalize to "only allow construction from types containing no lifetimes except those inherent in the generic type parameter on Opaque<T>"
Is that correct? Or am I failing to see some other unsound gotcha?
The API I sketched depends on T: 's implying ErasedBaseType: 's, basically, so you need something that enforces that. Vec<T>: 's iff T: 's, so that qualifies, but yes -- the real requirement is something like