Cast empty Vec<&'a T> to Vec<&'static T>

Is there some way to cast empty Vec<&'a T> to Vec with static lifetime? There are no elements in it, I just need to save capacity and reuse it in hot loop.
Example

trait Draw {}
struct Render {
    buffer: Vec<&'static dyn Draw>,
}

impl Render {
    fn draw(&mut self, objs: &[&dyn Draw]) {
        let mut buffer = std::mem::take(&mut self.buffer);
        buffer.extend(objs);
        // work with buffer
        buffer.clear();
        self.buffer = buffer;
    }
}

Does this have a (preferably safe) solution?

Sorry, no. Lifetimes are determined and handled at compile-time, but the length of the Vec is only known at run-time. You could try Vec<Box<dyn Draw>> instead though

I understand that the length is handled at runtime, but is such a cast possible theoretically? Okay, if I need to check assert!(buffer.is_empty()). It's still much more efficient than boxing each object.

If you need to store temporary references in it, why would you want to mark them as 'static? Something doesn't seem to add up here.

Yeah, if you assert that, it should be okay to use Vec::from_raw_parts, but afaik there wouldn't be safely do that.

Hm, if you're mem::takeing self.buffer, how isn't buffer: Vec<&'static _>?

I don't mark temporary references as 'static, they are 'a, but the buffer is static.

The lifetime is being shrunk because of buffer.extend(objs);, where the references in objs point to values that can live shorter than 'static.

1 Like

I get that. My question is exactly why the elements in the buffer are marked as 'static if you want to put &'a dyn Draw in it?

1 Like

Will the rendered objects always be the same lifetime? If so, could you do

struct Render<'a> {
    buffer: Vec<&'a dyn Draw>,
}
1 Like

I can make a buffer 'a, then I need to create an lifetime for Render:

struct Render<'a> {
    buffer: Vec<&'a dyn Draw>,
}

But this lifetime does not make sense. The Render is completely static, it doesn't store any actual references.

It isn't completely static, it holds a buffer of &'a dyn Draws

Even if it doesn't hold any actual values between draw calls, that's still what its buffer is storing

1 Like

For the type system - yes. But actually - no. So I want to find a solution to this problem. Theoretically, I can do a cast Vec<&'a dyn Draw> to Vec<&'static dyn Draw>, but I want to know how safe it is.

There is no safe way to do that.

If you're willing to use unsafe, you could do

impl Render {
    fn draw(&mut self, objs: &[&dyn Draw]) {
        let mut buffer = std::mem::take(&mut self.buffer);
        buffer.extend(objs);
        // work with buffer
        buffer.clear();
        let (ptr, cap) = (buffer.as_mut_ptr(), buffer.cap());
        std::mem::forget(buffer);
        self.buffer = unsafe { Vec::from_raw_parts(ptr, 0, cap) };
    }
}

which should be sound

Thank you, this is what I need.

There is a safe way to do that. Check out the asm generated to ensure it doesn't allocate/deallocate.

pub fn conv<'a, 'b>(mut v: Vec<&'a i32>) -> Vec<&'b i32> {
    v.clear();
    v.into_iter().map(|_| &42).collect()
}
5 Likes

Wait seriously? That reuses the buffer? Awesome, I had no idea, thank you!

Well, if you're ok with something that works, but could potentially change in the future because it's just an implementation detail, you can use this:

pub fn foo<'a>(mut v: Vec<&'a i32>) -> Vec<&'static i32> {
    v.clear();
    v.into_iter().map(|_| -> &'static i32 { unreachable!() }).collect()
}

That looks like it'd need to reallocate, but nope, it doesn't: https://rust.godbolt.org/z/j6KM3M7a7

You can also confirm it by seeing that the capacity is preserved:

let x = Vec::with_capacity(123456);
let y = foo(x);
assert_eq!(y.capacity(), 123456);

https://play.rust-lang.org/?version=stable&mode=release&edition=2021&gist=08a15c321b5932373b56999b3b5d5714

Edit: Doh, Hyeonu beat me to it.

3 Likes

The optimization potential of iterators continues to amaze me

Why doesn't map trigger panic with unreachable?

That closure is called for each item, but if the Vec is empty, there's no items to call it on, and so it isn't called at all.

1 Like