I'm getting a bit lost with lifetime parameters in a theoretically simple scenario:
I have a struct that contains some data, and work will be done on this data. This struct can be chunked so the work can be done in parallel, and it implements a trait to expose this functionality. For this example I will call the trait Chunkable
and the specific struct MyChunkable
.
The chunks themselves also implement a trait, and so I will call the trait Chunk
and the specific struct MyChunk
.
The structs look like this:
struct MyChunkable {
data: Vec<u32>,
}
struct MyChunk<'a> {
data: &'a mut [u32],
}
The MyChunk
struct above contains a mutable reference to a subset of the data in MyChunkable
.
The trait Chunk
is simple, as is the implementation:
trait Chunk {
fn do_it(&mut self);
}
impl<'a> Chunk for MyChunk<'a> {
fn do_it(&mut self) {
self.data[0] = 0;
}
}
In this example I'm just setting the zeroth element of each chunk to zero for the implementation of the Chunk
trait.
The trait Chunkable
gets more interesting. I need to tie the lifetime of the data referenced in Chunk
to the lifetime of the data in Chunkable
, and so I need to specify a lifetime parameter 'a
on the trait so the implementation can make use of it. Perhaps there is a better way to do this? But this is what I've got:
trait Chunkable<'a, TChunk>
where
TChunk : Chunk
{
fn chunks_mut(&'a mut self) -> Vec<TChunk>;
}
impl<'a> Chunkable<'a, MyChunk<'a>> for MyChunkable {
fn chunks_mut(&'a mut self) -> Vec<MyChunk<'a>> {
self.data
.chunks_mut(10)
.map(|v| MyChunk { data: v })
.collect()
}
}
So far, so good. Next, for various reasons, I need a builder trait for Chunkable
, and an implementation:
trait ChunkableBuilder<'a, TChunkable, TChunk>
where
TChunk : Chunk,
TChunkable : Chunkable<'a, TChunk>
{
fn create(&self, count: u32) -> TChunkable;
}
struct MyChunkableBuilder {}
impl<'a> ChunkableBuilder<'a, MyChunkable, MyChunk<'a>> for MyChunkableBuilder {
fn create(&self, count: u32) -> MyChunkable {
MyChunkable {
data: (0..count).collect(),
}
}
}
This is fine, although the ChunkableBuilder
has been infected with the lifetime 'a
, even though it doesn't seem like the builder should really care about the lifetimes data it creates and returns.
Despite that, I can verify that all this works, like so:
fn main() {
let builder = MyChunkableBuilder {};
let mut chunkable = builder.create(100);
let chunks = chunkable.chunks_mut();
for mut chunk in chunks {
chunk.do_it();
}
print!("{:#?}", chunkable.data);
}
Now is where I get a bit lost. Let's say I have a Runner
trait. The implementation of this trait will do the work above and return the Chunkable
so I can read the data out of it later. Here is the trait:
trait Runner<TChunkable, TChunk>
where
for<'a> TChunkable: Chunkable<'a, TChunk>,
TChunk : Chunk
{
fn run(&self) -> TChunkable;
}
The struct that implements this trait owns the builder, and does the work. The generic type parameters are getting a bit out of hand here, but they compile ok.
struct MyRunner<TChunkableBuilder, TChunkable, TChunk>
where
for<'a> TChunkableBuilder: ChunkableBuilder<'a, TChunkable, TChunk>,
for<'a> TChunkable: Chunkable<'a, TChunk>,
TChunk: Chunk,
{
chunkable_builder: TChunkableBuilder,
_phantom_data: PhantomData<(TChunkable, TChunk)>,
}
impl<TChunkableBuilder, TChunkable, TChunk> Runner<TChunkable, TChunk>
for MyRunner<TChunkableBuilder, TChunkable, TChunk>
where
for<'a> TChunkableBuilder: ChunkableBuilder<'a, TChunkable, TChunk>,
for<'a> TChunkable: Chunkable<'a, TChunk>,
TChunk: Chunk,
{
fn run(&self) -> TChunkable {
let mut chunkable = self.chunkable_builder.create(100);
let chunks = chunkable.chunks_mut();
for mut chunk in chunks {
chunk.do_it();
}
chunkable
}
}
At least this all compiles fine until I try to write some code to actually use MyRunner
:
fn main() {
let runner = MyRunner {
chunkable_builder: MyChunkableBuilder {},
_phantom_data: PhantomData,
};
let chunkable = runner.run();
print!("{:#?}", chunkable.data);
}
At which point I get:
Compiling playground v0.0.1 (/playground)
error[E0599]: the method `run` exists for struct `MyRunner<MyChunkableBuilder, MyChunkable, MyChunk<'_>>`, but its trait bounds were not satisfied
--> src/main.rs:116:28
|
4 | struct MyChunkable {
| ------------------ doesn't satisfy `MyChunkable: Chunkable<'a, MyChunk<'_>>`
...
49 | struct MyChunkableBuilder {}
| ------------------------- doesn't satisfy `_: ChunkableBuilder<'a, MyChunkable, MyChunk<'_>>`
...
82 | struct MyRunner<TChunkableBuilder, TChunkable, TChunk>
| ------------------------------------------------------
| |
| method `run` not found for this struct
| doesn't satisfy `_: Runner<MyChunkable, MyChunk<'_>>`
...
116 | let chunkable = runner.run();
| ^^^ method cannot be called due to unsatisfied trait bounds
|
note: the following trait bounds were not satisfied:
`MyChunkable: Chunkable<'a, MyChunk<'_>>`
`MyChunkableBuilder: ChunkableBuilder<'a, MyChunkable, MyChunk<'_>>`
--> src/main.rs:95:32
|
92 | impl<TChunkableBuilder, TChunkable, TChunk> Runner<TChunkable, TChunk>
| --------------------------
93 | for MyRunner<TChunkableBuilder, TChunkable, TChunk>
| -----------------------------------------------
94 | where
95 | for<'a> TChunkableBuilder: ChunkableBuilder<'a, TChunkable, TChunk>,
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ unsatisfied trait bound introduced here
96 | for<'a> TChunkable: Chunkable<'a, TChunk>,
| ^^^^^^^^^^^^^^^^^^^^^ unsatisfied trait bound introduced here
note: the traits `Chunkable` and `ChunkableBuilder` must be implemented
--> src/main.rs:24:1
|
24 | trait Chunkable<'a, TChunk>
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
...
41 | trait ChunkableBuilder<'a, TChunkable, TChunk>
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
For more information about this error, try `rustc --explain E0599`.
error: could not compile `playground` due to previous error
I'm a bit stuck on where I go from here to fix the error, but this is where my current thinking is:
-
The lifetime
'a
seems to be very infectious, even though it is really only a bound on the data shared betweenMyChunk
andMyChunkable
. Is this normal? -
The generic parameters can get very busy very quickly, although I can accept that this is a result of trying to avoid using
dyn
and dynamic dispatch. -
The main error: It feels like the higher rank trait bounds on
MyRunner
are perhaps not quite tying all the lifetimes together correctly, but I'm not sure how to better specify this.
Any help or advice is appreciated..
This code is runnable here:
You'll see that if you comment out fn main()
at the bottom it compiles fine, and renaming fn main_this_works_fine()
to fn main()
will allow the code run as expected (without using the Runner
).