Getting PhantomData to have a 'static lifetime

I'm trying to implement a generic "id container" struct that is strongly typed. Basically its a struct with a generic parameter to hold the type and a small payload.

Since the generic parameter is unused within the struct, I need to add a PhantomData<T> member. When this parameter's type does not have a 'static lifetime, this affects the entire struct.

The following code that reproduces this issue:

use std::marker::PhantomData;

struct Tag<T>(usize, PhantomData<T>);
struct WithRef<'a>(&'a i32);

trait SomeTrait {}
impl<T> SomeTrait for Tag<T> {}

fn ok() {
    let obj = Tag::<i32>(1, PhantomData);
    let _dyn_obj: &(dyn SomeTrait + 'static) = &obj;
}

fn bad<'b>() {
    let obj = Tag::<WithRef<'b>>(2, PhantomData);
    let _dyn_obj: &(dyn SomeTrait + 'static) = &obj;
}

outputs this compiler error:

error[E0495]: cannot infer an appropriate lifetime due to conflicting requirements
  --> src\main.rs:15:15
   |
15 |     let obj = Tag::<WithRef<'b>>(2, PhantomData);
   |               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
note: first, the lifetime cannot outlive the lifetime `'b` as defined on the function body at 14:8...
  --> src\main.rs:14:8
   |
14 | fn bad<'b>() {
   |        ^^
note: ...so that the expression is assignable
  --> src\main.rs:15:15
   |
15 |     let obj = Tag::<WithRef<'b>>(2, PhantomData);
   |               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   = note: expected  `Tag<WithRef<'_>>`
              found  `Tag<WithRef<'b>>`
   = note: but, the lifetime must be valid for the static lifetime...
note: ...so that the expression is assignable
  --> src\main.rs:16:48
   |
16 |     let _dyn_obj: &(dyn SomeTrait + 'static) = &obj;
   |                                                ^^^^
   = note: expected  `&(dyn SomeTrait + 'static)`
              found  `&dyn SomeTrait`

https://doc.rust-lang.org/beta/std/marker/struct.PhantomData.html mentions that:

If your struct does not in fact own the data of type T, it is better to use a reference type, like PhantomData<&'a T> (ideally) or PhantomData<*const T> (if no lifetime applies), so as not to indicate ownership.

But neither PhantomData<&'a T>nor PhantomData<*const T> makes this code compile.

Is there any way to keep the generic parameter without loosing the 'static lifetime?

Thanks!

This isn't possible. If you have a Tag<&'a u32> for some short lifetime 'a, it is not possible for this value to exist after the lifetime 'a ends.

This is true even if the tag doesn't actually contain a &'a u32. The type parameter doesn't exist after the lifetime ends, so the type of your tag stops making sense.

There might be a way around this, but that would involve making global values of type T for each T you plan to use (globals have lifetime 'static) and using those. But unless there is no better design possible at all, I really would avoid this since designs like this have issues of their own.

I understand that lifetimes are often not thought of in this manner, but if 'a and 'b are different lifetimes, then &'a u32 and &'b u32 are different types. If T = &'a u32 then putting a value of that type in a global doesn't make sense — you would be dealing with the different albeit similar type &'static u32. In particular this would be equivalent to using WithRef<'static>.

I actually also expect other issues further down the line as well.
That coupled with the fact that I haven't tested any of this is why I said that there might be a workaround.

In any case it's a good idea to go with a different design, one that doesn't include borrows or explicit lifetimes in the definition of types and values.

Interesting, I wasn't thinking of the lifetime parameter as the lifetime of the type itself, but yeah, it makes sense.

With my design, the tag really doesn't care about the lifetime since it's used it to represent data that exists outside of the program. I'm thinking about that data, and it's type, as having a lifetime even larger than 'static... if this makes any sense.

I'll try a different design and will post the result here for completeness.

Thanks for the help!

Well, you can always restrict the generic parameter to accept only something sensible for your case:

struct Tag<T: 'static>(usize, PhantomData<T>);
struct WithRef<'a>(&'a i32);

fn bad<'b>() {
    let _obj = Tag::<WithRef<'b>>(2, PhantomData); // fail - WithRef<'b> is not 'static
}

Playground

1 Like

Thanks everyone for the answers!

I ended up adding 'static lifetime constraint as @Cerberuser suggested.

Since I still needed to use Tag with types similar to WithRef, I explecitely declare Tag<WithRef<'static>> which ends up representing the correct lifetime of what Tag represents.

However, I do have some cases where with generic code that takes a Tag and as input, then outputs a deserialized WithRef with a non 'static lifetime. I implemented the following trait to handle these cases:

trait ToLifetime<'other> {
    type AsOther;
}
impl<'other, 'a> ToLifetime<'other> for WithRef<'a> {
    type AsOther = WithRef<'other>;
}