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:
Initialize the Vec in the Solver with the data from the problem
Do the actual calculation using the Vec<Model<'p>>
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?
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.
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.
(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.)