Using a Generic Trait over S<T> enforces me to have T outlive S

103 views Asked by At

The boiled-down problem looks as follows:

use std::marker::PhantomData;

struct WorldState<'a> {
    state: &'a f64,
}

trait CalculateWorldState<T> {
    fn state_value(&mut self, input: &T) -> f64;
}


trait LearningAlgorithm<T> {
    fn print_learning_information(&self, &T);
}

struct EvolutionaryAlgorithm<F, T>
where
    F: CalculateWorldState<T>,
{
    //I need this since I only use T as a method parameter, I do not save it anywhere
    //T are different ways to represent the current worldstate and are
    //short-lived (new ones generated every frame)
    _p_: PhantomData<T>,
    //I don't actually need this one in the real example since I have
    //an instatiated version of type CalculateWorldState saved in the
    //struct but I use phantomdata for simplicity of the example
    _p: PhantomData<F>,
}

impl<F, T> LearningAlgorithm<T> for EvolutionaryAlgorithm<F, T>
where
    F: CalculateWorldState<T>,
{
    fn print_learning_information(&self, input: &T) {
        println!("My learning goes splendid!");
        //do something with &T by calling the object of type
        //CalculateWorldState which we have saved somewhere, but do
        //not save the &T reference anywhere, just look at it
    }
}

struct WorldIsInGoodState {}

impl<'a> CalculateWorldState<WorldState<'a>> for WorldIsInGoodState {
    fn state_value(&mut self, input: &WorldState) -> f64 {
        100.
    }
}

fn main() {
    let mut a: Box<LearningAlgorithm<WorldState>> =
        Box::new(EvolutionaryAlgorithm::<WorldIsInGoodState, WorldState> {
            _p: PhantomData,
            _p_: PhantomData,
        });
    {
        let state = WorldState { state: &5. };
        a.print_learning_information(&state);
    }
}

Playground.

The above code fails to compile:

error[E0597]: borrowed value does not live long enough
  --> src/main.rs:59:5
   |
57 |         let state = WorldState { state: &5. };
   |                                          -- temporary value created here
58 |         a.print_learning_information(&state);
59 |     }
   |     ^ temporary value dropped here while still borrowed
60 | }
   | - temporary value needs to live until here

WorldState<'a> is a very short-lived data type (one per frame), whereas LearningAlgorithm is a very long-lived data type (multiple games). But the way I implemented the thing, Rust is eager to believe, that every WorldState I pass to print_learning_information has to outlive the LearningAlgorithm.

What did I do wrong? How could this else be handled?

A few things I would not like to do:

  • Have WorldState contain a normal state (since in reality it contains a few vectors and not a f64 and I don't want to copy them around into WorldState structs when passing each player its own view of the world)
  • Just quit this project and start a new one (you all know it, after you invested some time, you don't want to just throw all the work away)
1

There are 1 answers

0
red75prime On

Your problem can be boiled down to

struct WorldState<'a> {
    state: &'a f64,
}

trait LearningAlgorithm<T> {
    fn print_learning_information(&self, &T);
}

struct EvolutionaryAlgorithm();

impl<T> LearningAlgorithm<T> for EvolutionaryAlgorithm
{
    fn print_learning_information(&self, input: &T) {
    }
}

fn main() {
    // scope a
    let mut a: Box<LearningAlgorithm<WorldState>> =
        Box::new(EvolutionaryAlgorithm());
    { // scope b
        let val = 5.;
        let state = WorldState { state: &val };
        a.print_learning_information(&state);
    }
}

Note that WorldState is a type constructor, not a concrete type. Lifetime elision allows you to write Box<LearningAlgorithm<WorldState>> with no lifetime parameter explicitly specified for WorldState, but it just means that the compiler selects some appropriate lifetime parameter.

In this case lifetime selected for WorldState is scope a, thus the type of a is Box<LearningAlgorithm<WorldState<'scope_a>>>. Consequently, state should have type WorldState<'scope_a>, and the reference it contains should be valid for scope a, but a value the reference points to exists only in scope b.

You need support for higher-kinded types to make your example work as it is, but Rust doesn't provide it.

The easiest solution is to get rid of WorldState's lifetime parameter by replacing reference with Rc. Maybe someone will come up with better solution.