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.