Inferred lifetime for dyn trait

Hello,

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?

Thank you in advance.

Unfortunately, I think the best you can do at the monent is this:

struct MyStruct<'a, T:'a> {
    vec: Box<dyn 'a+VecTrait<T>>
}

impl<'a, T:'a> MyStruct<T> {
    fn new(vec: Box<dyn VecTrait<T> + 'a>) {
        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]));
}

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…)

2 Likes

TL;DR: You can't avoid a lifetime parameter if you want to be able to coerce non-'static Vec<T> to some dyn VecTrait<T> + 't in your data structure (but you can make T: 't implied).

  1. 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.

  2. 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)
  3. Box<dyn Trait> outside of function bodies means Box<dyn Trait + 'static>

  4. So if you want to support cases other than Impl: 'static, you need a lifetime parameter on MyStruct<..>

    struct MyStruct<'a, T> {
        vec: Box<dyn VecTrait<T> + 'a>,
    }
    
  5. The presence of dyn VecTrait<T> + 'a doesn't imply T: 'a because you can:

    impl<'a> VecTrait<&'a str> for () {}
    
    fn example<'not_static>() {
        let _: &(dyn VecTrait<&'not_static str> + 'static) = &();
    }
    

    where Impl: 'static but it is not true that T: 'static, for example

  6. So if you want that requirement, you have to add it explicitly

    //                  vvvvv
    struct MyStruct<'a, T: 'a> {
        vec: Box<dyn VecTrait<T> + 'a>,
    }
    
  7. 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

    // Implied: T: 'a
    impl<'a, T> MyStruct<'a, T> {
        fn new(vec: Box<dyn VecTrait<T> + 'a>) {
            Self { vec };
        }
    }
    
  8. Alternatively you can work the implied bound into the trait by adding a lifetime and something that implies T: 'a (or maybe like so)

    • 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

  1. explicit or inferred ↩︎

3 Likes

I guess this still applies, but at another position than I talked about in (1) above. It would be something like

struct MyStruct<T> {
    vec: Box<dyn Vec<T> + supremum(T)>
}

(And T: supremum(T) is implicit for every T.)

2 Likes

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.

...something like

struct MyStruct<T> {
   vec: Box<dyn Vec<T> + supremum(T)>
}

The supremum lifetime would be a beautiful feature!

It's probably soundly achievable with unsafe, where you

  • Have some Opaque<T>(Box<dyn VecTrait<T> + 'static>)
    • Private field, in it's own module, only allow construction from Vec<T>
  • Have some accessor fn access<'s>(&'s self) -> &'s dyn VecTrait<T>
    • Implicitly, T: 's
    • The unsugared return type is &'s (dyn VecTrait<T> + 's)

I also had this idea, which avoids lifetimes everywhere but trades it for trait bounds everywhere.

struct MyStruct<V> {
    vec: V,
}

impl<V> MyStruct<V> {
    fn useful_stuff<T>(&self) where V: VecTrait<T> {}
}

impl<T> VecTrait<T> for Box<dyn VecTrait<T> + '_> {}
2 Likes

only allow construction from Vec<T>

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?

Thanks again!

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

fn new<'t, V, T>(vec: V) where V: 't + VecTrait<T>, T: 't -> Self { .. }

(Because you can coerce V to dyn VecTrait<T> + 't with these bounds, which is the limit of what the access method may return.)

Or just...

fn new_from_box<'t, T: 't>(_: Box<dyn VecTrait<T> + 't>) -> Self { .. }
2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.