Lifetime design (wo transmute)

Hi,

I'm struggling to find a design that allows me to avoid using transmute to cheange lifetimes - but it feels as if it should be possible.

Short description
I have a solver for a mathematical problem where there is a lot of referencing various arrays and the like - and I want to avoid the extra dereference from a Arc, i.e. the solver should operate on &'p[T] s instead of Arc<Vec> s.

There is a lot of temporary structs used during the solving - they can be re-used between solving one Problem and the next Problem by storing them in a Solver to avoid allocations - but some of them need to be initialized with data (containing references) from the Problem currently beeing solved.

More in detail
I have a a struct Problem<'p> that describes the problem to be solved (referencing other data gurenteed to have a lifetime of 'p, as an example the problem has a field models: &'p Model<'p>). I furhter have a Solver which is created once and used for multiple problems (i.e. the solver should NOT be bound to exist only for the lifetime 'p of a partiuclar problem).

When creating the Solver I want to create an allocation e.g. Vec<Model<_>> (<-this is the problem - what can I put here) that is then used to during a calculation store Model<'p> s.

Now, the problem is that each problem has it's own lifetime 'p that I cannot parameterize the "global" solver with it. However - for my usage I could as part of the calculation do the following:

  1. Initialize the Vec in the Solver with the data from the problem
  2. Do the actual calculation using the Vec<Model<'p>>
  3. Before returning truncating the array in the Solver - to ensure that the Models stored in it do not outlive the function/lifetime.

The only way I have figured to do this in practice is use 'static as the lifetime in the Solver, transmute to 'p during calculation and transmute back (after truncating). However this is likely unsound (though I cant see why) and a smell.

It feels like it should be possible to express this without unsafe - does anyone have any suggestion for other ways to organize this?

Thanks

This sounds like a buffer reuse issue when using references having different lifetimes.
Maybe if you shared some MRE, we can find a workaround.

1 Like

Yes, that is a good desciption of the problem I'm having - i will create an example and attach.

Thanks

This is the step that’s tricky with lifetimes: There aren’t many good ways to ensure that safety-critical cleanup code gets run vs. just dropping the object that was supposed to run the finalization code.

You’ll probably want to split your solver code into two separate structs, one that’s responsible for holding onto (mostly) untyped buffers and another that’s tied to the problem’s lifetime. There’ll probably still be unsafe around, but you can at least keep it encapsulated in a simple subsystem away from the complicated logic.

Does this example make it clearer?

Here I have "solved" it by using unsafe transmutes

One thought I just had was to define a function that accepts a Cache and a closure that:

  1. Transmutes the Cache to the right lifetime in a local variable
  2. Runs the calculation (by calling the closure)
  3. Truncates to ensure that no "dangling data" is left

This should allow me to ensure that there is no data living with to long a lifetime.

Does that (combined with the sort of transmutes in the example) make sense? Or am i missing any possibility for unsafety/unsoundness?

You can perform this operation completely safely, thanks to a trick built into Vec: the Vec::into_iter() iterator is capable of reusing its memory allocation if it is used to create another Vec, even if the type is different.

impl<'p> Cache<'p> {
    fn reuse<'p2>(mut self) -> Cache<'p2> {
        self.data.clear();
        Cache {
            data: self.data
                .into_iter()
                .map(|_| unreachable!("vec has zero items"))
                .collect(), // reuses allocation consumed by into_iter
        }
    }
}

...

impl Solver {
    fn run_calc<'p>(&mut self, p: &Problem<'p>) -> i32 {
        let mut cache = self.c.take().unwrap().reuse();
        ...
        self.c = Some(cache.reuse());
    }
}

(Current events: T-libs-api is currently discussing the idea of adding a method to Vec to implement this pattern explicitly and somewhat more generally.)

5 Likes

Thanks, thats a neat solution, I did know that Vec::into_iter could recycle - but never thought of using it like that.