Hi,
I have some type-erased pointer NonNull<u8>
to some data.
(it is data stored in bevy's ECS, and the pointer is Ptr
which is a wrapper around NonNull<u8>
)
I would like to clone the data so that I can store it somewhere else.
Is it possible to do in rust?
I'm not sure how to type-erase a function like fn clone(&self) -> Self
There's GitHub - dtolnay/dyn-clone: Clone trait that is object-safe
But Ptr already implements Clone, though of course that clones the (shared) Ptr itself, rather than the data it points to.
But Ptr has an unsafe method deref() that will get you &T
at which point you could clone.
Someone more familiar with Bevy might be able to give more targeted advice. I only learned of Ptr's existence from reading this.
The dyn-clone
crate mentioned above is a good option if it satisfies your needs. If you need to write your own for some reason, like needing a different erased output type, @quinedot âs A tour of dyn Trait tutorial describes how to do it yourself .
1 Like
TL;DR: the usual trick consists of 2 steps:
Make an equivalent trait that does what you want but is object-safe
Apply a blanket impl that will make the compiler generate the impl for all (applicable) types, e.g.:
trait Cloneable<'a> {
fn dyn_clone(&self) -> Box<dyn Cloneable + 'a>;
}
impl<'a, T: Clone + 'a> Cloneable<'a> for T {
fn dyn_clone(&self) -> Box<dyn Cloneable + 'a> {
Box::new(self.clone())
}
}
Playground
1 Like
I would like to make it work via raw pointers
because what I get as input is Ptr
(which is basically a NonNull<u8>
that points to some data)
I tried doing something like this:
unsafe fn erased_clone<C: Clone>(data: Ptr) -> NonNull<u8> {
let cloned = data.deref::<C>().clone();
let mut temp = ManuallyDrop::new(cloned);
NonNull::from(&mut *temp).cast()
But miri complains, saying that the alloc in temp
gets deallocated at the end of the function.
Is it because the memory is freed at the end of the function, even if I called ManuallyDrop?
How can I do it?
Yes. It's on the stack. Leaving the function pops the stack. You've just prevented calling the destructor.
cBournhonesque:
How can I do it?
Return C
(i.e. cloned
), not NonNull<u8>
.
Or use Box::into_raw(Box::new(cloned))
to put in on the heap and get a pointer to that.
Actually I managed to do it like that (I need to also return a type-erased ptr)
unsafe fn erased_clone<C: Clone>(data: Ptr) -> NonNull<u8> {
let cloned: C = data.deref::<C>().clone();
let cloned_ptr = Box::leak(Box::new(cloned));
NonNull::from(cloned_ptr).cast()
}
and then I call Box::from_raw(data.cast::<C>().as_ptr());
to reclaim the memory.
When the box gets dropped, drop()
is called and the memory is freed.
Box::into_raw
is more semantically correct then (though I don't think there's any harm in this particular case).
You could also consider an API like beby_ptr::OwningPtr::make
which takes a closure rather than returning a pointer, thus avoiding a heap allocation.
system
Closed
August 24, 2024, 8:17am
10
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.