Transmute alternative for lifetimes

Hi,

Setting: I have numerical calcualtions using lots of cores and want to decrease the pressure on the allocator as much as possible (bottleneck).

One of the things I do is that i have a Datacahe like:

struct Cache{
    data: Vec<Mystruct> // There are actually many more than just one Vec here
}

Where i for every calculation reset the Cache.data (if not long enough) by using clone_from calculation specific data and finally and get a reference to the Data, &'c [MyStruct] that is passed to the actual calculation.

This works well.

However - I now have need for MyStruct to contain a reference to external data that is statically gurenteed (through lifetimes) to live longer than each individual calculation ('c) but not longer than the cache which is reused for multiple calculations.

Now I'm struggling to express this in Rust, since if I introduce the reference in MyStruct such that it becomes MyStruct<'c> I can no -longer reuse the Cache between different calcualtions (since the lifetimes do not match).

Now It should be safe to reuse data in Cache this way since I for every calculation clone_from into the data ensuring that no structs containing old (no longer valid) references is ever used.

Now I can "work around" the borrowing system here by transmuting the data Vec between the different liftetimes (say 'static in the Cache and transmuting to the calculation specific lifetime for each use) - but this feels like a bad solution (and is it even safe?)

Does anyone have any suggestion for how to express such a pattern in a better way in rust?

Thanks

I don't quite understand this sentence. it would be very helpful if you could show a sketch of the code structure or pseudo code.

when I heard the word "cache", I always think of memoization, but your "cache" sounds more like an pool-based or arena-based allocator. am I reading this right? if so, I think your "cache" may be better named "pool" or other allocation related thing?

please don't use transmute to by pass borrowck. it's should be the last thing to consider.

Not even that I'm afraid.
Lifetimes are a purely compile-time construct i.e there is no runtime component to them.

This means that a call to transmute has no way of targeting the right lifetime, and thus the compiler will (or at least should; I'm typing this on a smartphone so I haven't checked) reject it.
And even if the compiler somehow doesn't reject it, it's almost 100% guaranteed UB.

One potential solution here is to adopt an arena approach i.e. have your cache contain Vecs of each type of value you wish to cache, and just pass indices (which are Copy as well as small, so they're cheap to clone, copy and move) into those Vecs around rather than the actual values. This would sidestep the issues you've been having with borrow checking, at the cost of doing some array/vec indexing whenever you need the actual value itself rather than just its identity.

Hi,

Yes, indeed maybe "Pool" is a better name for it - it do not mean memoization but a way to keep allocations alive to not have to constantly have to alloc/free them.

Here is an example that maybe explains more (works).
But i cannot get it to work when introducing a refrence into MyStruct

Rust Playground (rust-lang.org)

While I do not disagree that transmute is a bad solution - it seems as if it should work - transmute in std::mem - Rust (rust-lang.org) - even lists it as av example to extrend or shorten a lifetime?

The arena approach is viable if I strted from scrach - but I ahve a large underlying codebase that expects slices to the data to work with (tried to create a minimal example in Rust Playground)

Assuming your after something like this.
from_raw_parts with a cast pointer to perform the transmute. The direct doc does not ban it, would need someone with deep language reference knowledge to confirm it isn't UB.

    struct Mystruct<'a> { external_ref: &'a u32 }
    struct Cache<'a> {
        data: Vec<Mystruct<'a>> // There are actually many more than just one Vec here
    }
    
    let mut cache = Cache { data: Vec::new() };

    'outer: for _ in 0..2 {
        let external = 0;
        
        let mut variant = std::mem::take(&mut cache.data);

        variant.push(Mystruct{ external_ref: &external });
        
        'inner: for _ in 0..2 {
            let work = 1 + variant[0].external_ref;
            assert_eq!(work, 1);
        }
        
        variant.clear();
        let mut v = std::mem::ManuallyDrop::new(variant);
        let p = v.as_mut_ptr() as _;
        let cap = v.capacity();
        cache.data = unsafe { Vec::from_raw_parts(p, 0, cap) };
    }

If you are in a position to change the type of Cache between calculations, you can take advantage of special machinery in collect::<Vec<_>> that will reuse the allocation

Something like this (untested, just adapting the code from the link):

struct Cache<'a> {
    data: Vec<MyStruct<'a>>
}

impl<'a> Cache<'a> {
    pub fn respec<'b>(self)->Cache<'b> {
        self.data.clear();
        Cache {
            data: self.data.into_iter().map(|_| unreachable!()).collect()
        }
    }
}
5 Likes

This is a really interesting concept! Never seen that before but at first glance it does seem like it would give me what I need.

I will try this out and see how it works

Thanks, This code I think I will need to study to make sure I understand what's going on

This does indeed work nicely - thanks! It’s perfect with an unsafe way to reach my goal :slight_smile:

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.