0

Suppose, I want to do the following - I want to have a struct, which holds 2 things: data and another struct, which references this data and implements a trait, which forces it to have method new, which takes a reference to this data.

Let's first focus on this second struct: since it holds a reference, we also need to specify lifetimes(even if we don't, let's do it anyway for educational purposes), so we get something like this, when creating it:

trait Trait<'a> {
    fn new(data_ref: &'a Data) -> Self; 
}

// Struct may be large, so we don't really want to clone it.
struct Data {
    value: u8,
}

struct TraitStruct<'a> {
    data_ref: &'a Data,
}

impl<'a> Trait<'a> for TraitStruct<'a> {
    fn new(data_ref: &'a Data) -> Self {
        return TraitStruct {
            data_ref,
        };
    }
}

Now, returning to the initial struct, I want to restrict the type of struct that it holds to one, that implements our trait, so naive way to do that would look like this:

struct StructHolder<T> where
    T: Trait
{
    data: Data,
    trait_struct: T,
}

impl<T> StructHolder<T> where 
    T: Trait
{
    fn new(data: Data) -> Self {
        let trait_struct = T::new(&data);
        return StructHolder {
            data,
            trait_struct,
        }; 
    }
}

This doesn't compile because Trait is generic over a lifetime, and compiler suggests using HRTB's:

   |
41 |     T: Trait
   |        ^^^^^ expected named lifetime parameter
   |
   = note: for more information on higher-ranked polymorphism, visit https://doc.rust-lang.org/nomicon/hrtb.html
help: consider making the bound lifetime-generic with a new `'a` lifetime
   |
41 |     T: for<'a> Trait<'a>
   |        +++++++      ++++
help: consider making the bound lifetime-generic with a new `'a` lifetime
   |
41 |     for<'a> T: Trait<'a>
   |     +++++++         ++++
help: consider introducing a named lifetime parameter
   |
40 ~ struct StructHolder<'a, T> where
41 ~     T: Trait<'a>
   |

No problem, suggestion is straightforward and we get:

struct StructHolder<T> where
    for<'a> T: Trait<'a>
{
    trait_struct: T,
}

impl<T> StructHolder<T> where
    for<'a> T: Trait<'a>
{
    fn new(data: Data) -> Self {
        let trait_struct = T::new(&data);
        return StructHolder {
            data,
            trait_struct,
        };
    }
}

This does remove the errors, but the main problem, which led me to asking this question is what happens, when I try to call new on StructHolder::<TraitStruct>:

fn main() {
    let result = StructHolder::<TraitStruct>::new(Data { value: 0 });
}

Compiler prints out the following error:

   |
5  | struct TraitStruct<'a> {
   | ---------------------- doesn't satisfy `TraitStruct<'_>: Trait<'a>`
...
21 | struct StructHolder<T> where
   | ---------------------- function or associated item `new` not found for this struct
...
41 |     let result = StructHolder::<TraitStruct>::new(Data { value: 0 });
   |                                               ^^^ function or associated item cannot be called on `StructHolder<TraitStruct<'_>>` due to unsatisfied trait bounds
   |
   = note: the following trait bounds were not satisfied:
           `TraitStruct<'_>: Trait<'a>`
note: the following trait must be implemented
  --> src/main.rs:1:1
   |
1  | trait Trait<'a> {
   | ^^^^^^^^^^^^^^^
   = help: items from traits can only be used if the trait is implemented and in scope
note: `Trait` defines an item `new`, perhaps you need to implement it
  --> src/main.rs:1:1
   |
1  | trait Trait<'a> {
   | ^^^^^^^^^^^^^^^

After some time thinking about it and trying things ous, I must admit, that I simply don't understand, what the error actually is. Text seems to say that Trait is not implemented for TraitStruct and that new is not found for StructHolder<T> and I flat out don't understand what is wrong with impl blocks above that leads to this error.

Don't really know how to ask a good question here, just want to understand why this happens.

Also, I think, I know how to try to fix this by wrapping the struct inside StructHolder in a Box for example, but it feels like there is a solution, which doesn't use indirection and it would be interesting to know how it works.

E_net4
  • 27,810
  • 13
  • 101
  • 139

1 Answers1

2

The problem is you promise to provide a for<'a> T: Trait<'a> but you only provide a T::new(&'_ data) = impl Trait<'_> borrowing from a local variable. Since '_ doesn't outlive 'a which could also be something like 'static that's not allowed.

In general I think what you want isn't really possible because it would require you to hold both Data and TraitStruct in the same struct but since TraitStruct references Data that'd be a self referential struct.

cafce25
  • 15,907
  • 4
  • 25
  • 31