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.
H2CO3
November 1, 2021, 8:47am
4
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::take
ing self.buffer
, how isn't buffer: Vec<&'static _>
?
I don't mark temporary references as 'static
, they are 'a
, but the buffer is static.
H2CO3
November 1, 2021, 8:50am
7
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
H2CO3
November 1, 2021, 8:51am
8
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
nanoqsh
November 1, 2021, 8:55am
10
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.
Cyborus
November 1, 2021, 8:56am
11
It isn't completely static, it holds a buffer of &'a dyn Draw
s
Even if it doesn't hold any actual values between draw calls, that's still what its buffer is storing
1 Like
nanoqsh
November 1, 2021, 9:00am
12
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.
Cyborus
November 1, 2021, 9:01am
13
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
nanoqsh
November 1, 2021, 9:06am
14
Thank you, this is what I need.
Hyeonu
November 1, 2021, 9:11am
15
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
Cyborus
November 1, 2021, 9:12am
16
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
Cyborus
November 1, 2021, 9:15am
18
The optimization potential of iterators continues to amaze me
nanoqsh
November 1, 2021, 9:16am
19
Why doesn't map
trigger panic with unreachable
?
Cyborus
November 1, 2021, 9:17am
20
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