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

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

Because the vector has zero elements.

2 Likes

Oh, exactly. Now it's all clear

If you're curious, this one is the library doing a bunch of work to detect the cases, with things like

Because it was just cleared, so it's never actually called.

(Hyeonu used &42 because that makes the types work out more directly, but it wasn't called either. unreachable!() is -> !, so it can coerce to anything.)

2 Likes

Hence "unreachable"

That is so awesome, thank you!

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.