Lifetimes in bounds

I'm wondering if I'm misusing lifetimes with generics. This pattern it's usually found in my code. ATrait is a trait with a method whose result must outlive the object (in real life is not an usize but a huge struct). However, when using this trait in a type bound, the compiler tells me to remove 'a or add a PhantomData. I have used PhantomData as much as necessary but I'm getting worried about it.

pub trait ATrait<'a>{
    fn return_a() -> &'a usize;
}

pub struct AStruct<'a, A : ATrait<'a>>{
    a : A,
}

fn main() {    
}

The compiler says:

error[E0392]: parameter `'a` is never used
 --> src/main.rs:8:20
  |
8 | pub struct AStruct<'a, A : ATrait<'a>>{
  |                    ^^ unused parameter
  |
  = help: consider removing `'a`, referring to it in a field, or using a marker such as `PhantomData`

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

So I have just changed it to:

use std::marker::PhantomData;

pub trait ATrait<'a>{
    fn return_a() -> &'a usize;
}

pub struct AStruct<'a, A : ATrait<'a>>{
    a : A,
    _a : PhantomData<&'a A>
}

fn main() {    
}

Don't do that. Consider removing the lifetime from the trait, or using a HRTB as in:

struct AStruct<A> {
    a: A,
}

impl<A> AStruct<A>
where
    for<'a> A: Atrait<'a>,
{ ... }

Note also how you usually should not put where bounds on the struct definition. The impl blocks are enough

What's the difference between the HRTB and the PhantomData? To me is cleaner the HRTB because you do not need to worry about phantom fields when instantiating but I'm not sure about the differences. They look the same.

Not putting where bounds on the struct definition is what I was thinking about. The only difference is that I will be allowing to create useless objects, without applicable impl blocks. Then, when will be idiomatic to put where bounds on the struct definition?

Only if you need information from the trait to determine the struct layout. For example:

struct CachedIter<I:Iterator> {
    iter: I,
    seen: Vec<I::Item>
}

Thanks for that example

What exactly are you trying to do with ATrait? Returning a reference with a lifetime from an associated function with no self parameter doesn't make a whole lot of sense most of the time. If you want return_a to return a reference to something the type already contains, you don't need explicit lifetimes at all.

Playground

pub trait ATrait {
    fn return_a(&self) -> &usize;
}

pub struct AImpl(usize);

impl ATrait for AImpl {
    fn return_a(&self) -> &usize {
        &self.0
    }
}

pub struct AStruct<A> {
    a: A,
}

impl<A: ATrait> AStruct<A> {
    fn task(&self) {
        let a_ref = self.a.return_a();

        println!("1: {a_ref}");
        println!("2: {a_ref}");
    }
}

fn main() {
    let a_struct = AStruct { a: AImpl(12) };

    a_struct.task();
}
2 Likes

The difference is that the lifetime does not get annotated on the struct with HRTB. This has all sorts of consequences, because if a struct is annotated with a lifetime, then the struct must be destroyed before the end of that lifetime.