Clone type-erased data

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:

  1. Make an equivalent trait that does what you want but is object-safe
  2. 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.

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[1]).


  1. If you let a &C escape from the &mut C you got from leak, the lifetimes could be problematic ↩ī¸Ž

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.

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.