I have a process that takes several steps. The steps are defined below as unit structs and have associated data via the Step trait. Each step has data which can be transformed by each runner. And it has a associated NextStep which is the next step. Note Step2's Data has a lifetime and for this to work Step2 struct has some lifetime shenanigans.
use std::marker::PhantomData;
trait Step {
type Data;
type NextStep: Step;
}
struct LifetimeData<'a>(&'a mut i32);
// The definitions of Steps are not important, these are used as marker types
struct Step1;
// Here Step2 needs a lifetime because of its associated data
struct Step2<'a>(PhantomData<&'a ()>);
struct Step3;
impl Step for Step1 {
type Data = ();
// This shouldn't be 'static, not sure what and how I should qualify the lifetime of Step2 thought...?
type NextStep = Step2<'static>;
}
impl<'a> Step for Step2<'a> {
type Data = LifetimeData<'a>;
type NextStep = Step3;
}
impl Step for Step3 {
type Data = ();
type NextStep = ();
}
// Needed as a final step ...
impl Step for () {
type Data = ();
type NextStep = ();
}
there is a issue with the declaration on Step1::NextStep which will be explained later
From these steps there are runners. They act over the data available and can transform, read and do processer tings. After each step and the runners for that step can take themselves and produce runners for the next step. Note that the runner should outlive the data S::Data (e.g &mut self lifetime > data: &mut S::Data lifetime) so they cannot take a reference from the step data and pass it to the next step as it may be invalided after the step (I am not sure whether I have got the lifetimes right here for that to be forbidden).
trait StepRunner<S: Step> {
fn do_thing(&mut self, data: &mut S::Data);
fn into_next_step(self: Box<Self>) -> Vec<Box<dyn StepRunner<S::NextStep>>>;
}
There is a trait for generating the step runner. There is then a run function which takes generators for each step, creates runners at each step and runs over the data. Generating runners works and the first step runners run fine.
trait StepRunnerGenerator<S: Step> {
fn generate(&mut self) -> Vec<Box<dyn StepRunner<S>>>;
}
fn run(
first_step_runners_generators: Vec<Box<dyn StepRunnerGenerator<Step1>>>,
second_step_runners_generators: Vec<Box<dyn for<'a> StepRunnerGenerator<Step2<'a>>>>,
third_step_runners_generators: Vec<Box<dyn StepRunnerGenerator<Step3>>>,
) {
let mut first_step_runners = first_step_runners_generators
.into_iter()
.flat_map(|mut gen| gen.generate())
.collect::<Vec<_>>();
first_step_runners
.iter_mut()
.for_each(|runner| runner.do_thing(&mut ()));
let mut value = 4_i32;
let mut third_pass_runners = {
let mut second_step_runners_generators = second_step_runners_generators
.into_iter()
.flat_map(|mut gen| gen.generate())
.chain(first_step_runners.into_iter().flat_map(|runner| runner.into_next_step()))
.collect::<Vec<_>>();
// The compiler doesn't like this because the `.chain(first_step_runners.into_iter().flat_map(|runner| runner.into_next_step()))`
// statement above returns a StepRunner<Step2<'static>>. And that 'static requires data to be
// accessible for the rest of the program
let mut data = LifetimeData(&mut value);
second_step_runners_generators
.iter_mut()
.for_each(|runner| runner.do_thing(&mut data));
third_step_runners_generators
.into_iter()
.flat_map(|mut gen| gen.generate())
.chain(second_step_runners_generators.into_iter().flat_map(|runner| runner.into_next_step()))
.collect::<Vec<_>>()
};
value += 1;
third_pass_runners
.iter_mut()
.for_each(|runner| runner.do_thing(&mut ()));
}
The problem is in transforming step 1 runners to step 2. See the Step implementation for Step1 current is written with a 'static lifetime:
impl Step for Step1 {
...
type NextStep = Step2<'static>;
}
It means that the step runners from second_step_runners_generators.into_iter().flat_map(|runner| runner.into_next_step()) have their data's lifetime as LifetimeData<'static>.
And this causes let mut data = LifetimeData(&mut value); needing to live for 'static, and means that value += 1; cannot be done because it is being mutabily borrowed at that time :(
How can I make the lifetime of <Step1 as Step>::NextStep to not be 'static so the run function works...? Does this a case require generic_associated_types? Is there a better solution that works today on stable...?