Recycling vecs using transmute

Hmm, came across this (more general) idea:

fn recycle_vec<A, B>(mut v: Vec<A>) -> Vec<B> {
    const {
        use std::alloc::Layout;

        let la = Layout::new::<A>();
        let lb = Layout::new::<B>();

        assert!(la.size() == lb.size(), "Size of types must match");
        assert!(la.align() == lb.align(), "Alignment of types must match");
    }

    v.clear();

    // SAFETY: vector cleared, no elements affected, layout equal
    unsafe { std::mem::transmute(v) }
}

Artificial usage example:

    let mut vec = Vec::with_capacity(10);

    loop {
        let s = String::from("a b c");
        vec.extend(s.split(' '));

        vec = recycle_vec(vec);
    }

What do you think?

Maybe an extension trait will beneficial: There are other candidates, like HashMap etc. This problem occurs occasionally in different forms, for example: Please help me let me keep my allocation

This is not guaranteed to work, Vec<A> and Vec<B> could have their fields laid out in different orders. For example, Vec<A> could be (ptr, len, cap) and Vec<B> could be (ptr, cap, len).

Instead of transmute, use Vec::from_raw_parts.

let mut v = ManuallyDrop::new(v);
let len = v.len();
let cap = v.capacity();
let ptr = v.as_mut_ptr();
Vec::from_raw_parts(ptr.cast(), len, cap)

You can also do this with safe code,

v.clear();
v.into_iter().map(|_| unreachable!()).collect()

This works, because Vec::from_iter will try to reuse the allocation when the layouts are compatible.

12 Likes

Wow, of course, that's subtle! Glad, I'm asked, Thanks!

There's also into_raw_parts (only stabilized with 1.93).

6 Likes

oh wow! I didn't realize that stabilized already. Amazing

And on nightly there is now Vec::recycle which is pretty much exactly this.

4 Likes