1

I have following code:

trait T<GT> {
    type AT;

    fn foo(&self);
}

struct AbstractT<GT, AT> {
    t: Box<dyn T<GT, AT = AT>>,
}

impl<GT, AT> T<GT> for AbstractT<GT, AT> {
    type AT = AT;

    fn foo(&self) {
        self.t.foo();
    }
}

fn boxed_abstract<GT, TT: T<GT> + 'static>(tt: TT) -> Box<dyn T<GT, AT = TT::AT>> {
    Box::new(AbstractT { t: Box::new(tt) })
}

playground

Which throws these errors:

error[E0310]: the associated type `<TT as T<GT>>::AT` may not live long enough
  --> src/lib.rs:20:5
   |
20 |     Box::new(AbstractT { t: Box::new(tt) })
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = help: consider adding an explicit lifetime bound `<TT as T<GT>>::AT: 'static`...
note: ...so that the type `AbstractT<GT, <TT as T<GT>>::AT>` will meet its required lifetime bounds
  --> src/lib.rs:20:5
   |
20 |     Box::new(AbstractT { t: Box::new(tt) })
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error[E0310]: the parameter type `GT` may not live long enough
  --> src/lib.rs:20:5
   |
19 | fn boxed_abstract<GT, TT: T<GT> + 'static>(tt: TT) -> Box<dyn T<GT, AT = TT::AT>> {
   |                   -- help: consider adding an explicit lifetime bound...: `GT: 'static`
20 |     Box::new(AbstractT { t: Box::new(tt) })
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
note: ...so that the type `AbstractT<GT, <TT as T<GT>>::AT>` will meet its required lifetime bounds
  --> src/lib.rs:20:5
   |
20 |     Box::new(AbstractT { t: Box::new(tt) })
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

If I remove GT from everywhere, it compiles just fine, but with GT it fails with a bunch of lifetime errors. It seems that existence of GT should not affect lifetimes of either dyn T or T::AT (because it's not used in them), but it apparently does. Likewise, lifetime of dyn T should not depend lifetimes of GT or AT but it apparently does.

Am I missing something or is it a lifetime inference problem?

pretzelhammer
  • 13,874
  • 15
  • 47
  • 98
FLashM
  • 23
  • 4

1 Answers1

1

You might be missing something. Rust automatically infers lifetime bounds for trait objects, in the case of boxed trait objects the automatically inferred lifetime bounds is 'static. For example, this what the Rust compiler sees when it looks at your code:

trait T<GT> {
    type AT;

    fn foo(&self);
}

struct AbstractT<GT, AT> {
    // notice the added "+ 'static" below
    t: Box<dyn T<GT, AT = AT> + 'static>,
}

impl<GT, AT> T<GT> for AbstractT<GT, AT> {
    type AT = AT;

    fn foo(&self) {
        self.t.foo();
    }
}

// notice the added "+ 'static" in the return type
fn boxed_abstract<GT, TT: T<GT> + 'static>(tt: TT) -> Box<dyn T<GT, AT = TT::AT> + 'static> {
    Box::new(AbstractT { t: Box::new(tt) })
}

To make it compile we just need to add more explicit 'static bounds on all your generic types like so:

trait T<GT> {
    type AT;

    fn foo(&self);
}

struct AbstractT<GT, AT> {
    t: Box<dyn T<GT, AT = AT>>,
}

impl<GT, AT> T<GT> for AbstractT<GT, AT> {
    type AT = AT;

    fn foo(&self) {
        self.t.foo();
    }
}

fn boxed_abstract<GT, TT>(tt: TT) -> Box<dyn T<GT, AT = TT::AT>>
    where TT: T<GT> + 'static, GT: 'static
{
    Box::new(AbstractT { t: Box::new(tt) })
}

playground

The reason why we need all these 'static bounds is simple: a container type can only be bound by 'static if all of the types inside of it are bound by 'static and so on recursively.

Further reading:

Update

If you don't like the 'static requirement you can make AbstractT generic over lifetimes by adding an explicit lifetime annotation to its type, like so:

trait T<GT> {
    type AT;

    fn foo(&self);
}

struct AbstractT<'a, GT, AT> {
    t: Box<dyn T<GT, AT = AT> + 'a>,
}

impl<'a, GT, AT> T<GT> for AbstractT<'a, GT, AT> {
    type AT = AT;

    fn foo(&self) {
        self.t.foo();
    }
}

fn boxed_abstract<'a, GT, TT>(tt: TT) -> Box<dyn T<GT, AT = TT::AT> + 'a>
    where TT: T<GT> + 'a, GT: 'a
{
    Box::new(AbstractT { t: Box::new(tt) })
}

playground

pretzelhammer
  • 13,874
  • 15
  • 47
  • 98
  • Well, T is not a container. It does not contain either GT or AT. Its lifetime should not put requirements on GT or AT lifetime, right? I don't want GT or AT to be static. And without GT rust is perfectly capable of understanding that T lifetime does not depend on AT lifetime. – FLashM May 19 '20 at 18:45
  • So my question is: 1. Why adding GT makes rust think that T lifetime is constrained by AT lifetime? 2. How to work around it, i.e. have `T: 'static` while AT/GT are not. – FLashM May 19 '20 at 18:48
  • @FLashM You're right, AT's lifetime doesn't need to be constrained by T's lifetime, I've updated my answer to reflect that. Also, I updated my answer to include a version of the solution without any dependencies on 'static. – pretzelhammer May 19 '20 at 19:20
  • If you preserve the original `TT: 'static` constraint you don't need to add a parameter to `AbstractT` in order to make `boxed_abstract` work: [example](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=836c933c7993cd31791ffc802cb16fcb). However, you wouldn't be able to stuff the returned `Box` into another layer of `AbstractT` because `AbstractT` itself requires `'static`. – trent May 19 '20 at 20:46
  • Hm. Interesting, so restricting GT relieves requirement on AT. However, question on GT still stands. GT lifetime does not affect T lifetime, is there a way to make boxed_abstract work without restricting GT lifetime? – FLashM May 20 '20 at 18:24