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
.
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?
Will the rendered objects always be the same lifetime? If so, could you do
struct Render<'a> {
buffer: Vec<&'a dyn Draw>,
}
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 Draw
s
Even if it doesn't hold any actual values between draw calls, that's still what its buffer is storing
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()
}
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);
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.
Because the vector has zero elements.
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 clear
ed, 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.)
Hence "unreachable"
Cyborus:The optimization potential of iterators
If you're curious, this one is the library doing a bunch of work to detect the cases, with things like
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.