#`&'static dyn T` has stricter lifetime requirements than `Box<dyn T>`

50 messages · Page 1 of 1 (latest)

rain lantern
#

The following code compiles:

trait GenericTrait<T> {}

struct GenericStruct<T> {
    func: Box<dyn GenericTrait<T> + 'static>,
}

However, the following doesn't:

trait GenericTrait<T> {}

struct GenericStruct<T> {
    func: &'static (dyn GenericTrait<T> + 'static),
}

Instead we get an error:

error[E0310]: the parameter type `T` may not live long enough
 --> src/lib.rs:4:11
  |
4 |     func: &'static (dyn GenericTrait<T> + 'static),
  |           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |           |
  |           the parameter type `T` must be valid for the static lifetime...
  |           ...so that the reference type `&'static (dyn GenericTrait<T> + 'static)` does not outlive the data it points at
  |
help: consider adding an explicit lifetime bound
  |
3 | struct GenericStruct<T: 'static> {
  |                       +++++++++

For more information about this error, try `rustc --explain E0310`.

I don't understand why a lifetime bound would be necessary. It is already required that the type implementing GenericTrait<T> has only static lifetimes, why would the type parameter need to be static as well? Even more, why does this work with Box?

This came up in https://discord.com/channels/273534239310479360/1197554265992994846 , but I found this part quite curious.

worthy forge
#

it's already required that the type implementing GenericTrait<T> has only static lifetimes

True, but not the point

#

The point here is T itself

#

I'm guessing this is a well-formedness thing

#

&'a T, in order to exist, requires T: 'a.

#

In most contexts, this bound is implied, which is how you can just write fn foo<'a, T>(r: &'a T)

#

But it isn't always.

#

I'm guessing here, but it might be that &'a dyn Foo<T> ends up requiring T: 'a

#

However, Box<dyn Foo<T>> has no equivalent requirement to make

rain lantern
worthy forge
#

I'm not sure whether it's strictly necessary here, but I can imagine some traits might exist for which this requirement does matter

#

That, or it's actually unnecessary

rain lantern
#

I can understand why the compiler might think it needs it (it's a T behind a &'a, therefore we need T: 'a)

worthy forge
#

@high grail, help

#

I need a lifetime expert here

rain lantern
#

but T here is just a type parameter to a trait, it is completely unrelated to the lifetime of the data

worthy forge
#

Which of course requires T: 'static

rain lantern
#

those are all good arguments, but they would apply to Box as well

#

or wouldn't they?

#

actually maybe not? hm....

worthy forge
#

fn(Box<Self>) -> T would return an owned T, thus imposing no well-formedness requirements.

And fn(&self) -> &T called on a Box<dyn Trait> would require you to borrow and deref-coerce, you wouldn't expect to be able to get a longer lifetime out of it

high grail
#

I would boil this down to &'static fn(&'a str) failing unless 'a : 'static. Which is indeed kind of annoying / too conservative of a stance, but such are the rules of "well-formedness" in Rust

rain lantern
worthy forge
#

That might also be the case, I'm not 100% sure my arguments about well-formedness are actually strictly correct

high grail
#

So I don't think there is a critical reason for &'static fn(…) or &'static dyn TraitApiThatDoesNotProduceA<…> to require … : 'static, other than simplicity of implementation in the compiler, and more lightweight syntax here and there.
But there may be an edge case I may be overlooking.

high grail
#

I would say that in the &'x fn() -> T case, the need for T : 'x seems way more reasonable, yeah, so I agree with @worthy forge's example yes

worthy forge
#

Which suggests the need for this kind of WF bound is trait-specific

#

So this might be like trait variance: we only implement the most restrictive (and thus definitely sound) option

high grail
#

Which, thus, in the trait case, requiring T : 'x for dyn Trait<T> : 'x to hold, at least reduces the semver issues that the following diff would otherwise cause:

  trait Trait<T> {
      fn consume(&self, _: T);
+     fn produce(&self) -> &T;
  }
high grail
worthy forge
#

Perhaps we could add some way to express that a trait opts out of this? Idk, a : NonWellFormed supertrait or a #[no_well_formedness_bounds] attribute, but that would have to be RFC'd and opt-in

rain lantern
#

I think primarily I'd want better diagnostics for this, because the argument in the error message is just wrong

#

but how does one explain this in simple terms

nova meteor
#

Yeah I'm doing my best to follow but there's a few terms here and there that I'm not familiar with.

worthy forge
rain lantern
#

so that the reference doesn't outlive the data it points to

#

that part

worthy forge
#

Ye, that one is a lie, granted

high grail
#

What would you say of:

error[E0310]: the parameter type `T` may not live long enough
 --> src/lib.rs:4:11
  |
4 |     func: &'static (dyn GenericTrait<T> + 'static),
  |           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |           |
  |           the parameter type `T` must be valid for the 'static lifetime...
  |           ...so that the reference type `&'static (dyn GenericTrait<T> + 'static)` does not outlive the data it points at
  |
note: in the case of `dyn Trait`s, this constraint may over-restrictively require for each
      individual generic parameter (such as `'a` or `T` in `dyn Trait<'a, T>`),
      to be valid for the 'static` lifetime.
  |
help: consider adding an explicit lifetime bound
  |
3 | struct GenericStruct<T: 'static> {
  |                       +++++++++

For more information about this error, try `rustc --explain E0310`.
little adder
#

I find it odd that dyn Trait<T> + 'static doesn’t itself introduce the well-formedness bounds

#

Since “usable-for 'static” typically implies “does not contain non-'static”

rain lantern
#

yeah I had some time to think and I agree. Something doesn't add up here, I'll play around with it a bit

little adder
#

It looks like “usability” lifetimes do not actually constrain the lifetimes a trait’s type parameters are allowed to contain

#

Only the Self generic is so constrained

rain lantern
#

Something I think is interesting:

impl<'a> LifetimeStruct<'a> {
    fn return_static(self) -> &'static LifetimeStruct<'a> { 
        Box::leak(self.into())
    }
}

this compiles, and it seems it adds an implicit where 'a: 'static bound to the function

rain lantern
#

It would have to hold a T: 'a internally, which would make it not dyn Trait<T> + 'static