Copying DST objects from heap to new allocation

How does one deep copy properly from heap a dynamically sized trait objects?

I've been reading a lot for the past week and a half and I believe I'm still not understanding whether I've been close to solving my solution or whether I'm on the right path.

So I'm having the following actors in my scenario:

pub type CustomType = Box<dyn SomeTrait + Send + Sync + 'static>

// due to async trait impl Self is ?Sized
#[async_trait]
trait SomeTrait: Send {
    async fn some_async_fn(&self) -> bool;
}

#[derive(Clone)]
struct SomeStruct {
    vec_hold: Vec<CustomType>
}

I'm also having a function that should return an owned copy of this Vec<CustomType>, therefore I have to impl Clone for CustomType {} where I decided to try copy the values from heap memory into a new allocation and return the object.
What I tried so far was creating a new allocation with align_of_val(self) and size_of_val(self) then copy_nonoverlaping(self, dst, size_of_val(self)) and return ptr::read(dst), I've tried a dozen of different variants before realizing that write/read are attempting at moving memory unlike copy_nonoverlaping (to the guys who wrote the documentation - thank you! your work is highly appreciated), after a while I've tried copying bytes into a Vec that would be casted to the object finally - couldn't figure out how to cast it, then finally I've decided to give a go with MaybeUninit.

Results:

  • In my best case scenarios the app crashes after (almost) 2 successful memory copies with heap corruption (undefined behavior probably due to improper cleanup after use of allocated memory, I've tried drop_in_place after use) (and I say almost 2 successful because the operation gets executed almost till the end and then suddenly crashes)
  • In my worst case scenarios it crashes with access violation (that's when I know that I've really screwed up with playing with the raw pointers)

My research on the case:
I've found easily the solutions of people who easily bypass the problem by adding another step of cloning and also found a crate that does that for you, however these two are covering cases where Self: Sized while I need it for ?Sized.

Any explanation and idea is highly appreciated, also you are free to assume that I know nothing about memory management, because its true and I'm currently learning. I've been recently reading about memory alignment and size of types and how they reflect the alignment and about rust afinite memory management.

Don't try to implement Clone by doing manual memory allocation & memory copies. As you're noticing, this will result in all kinds of problems. You should just rely on the type system to clone. In particular, your code can't know how to properly clone any type that implements SomeTrait. So you need implementers of SomeTrait to tell you.

The easiest way is to add a method on SomeTrait that does what you want:

trait SomeTrait {
    fn clone_dyn_boxed(&self) -> Box<dyn SomeTrait>;
}

Then, you can implement Clone in terms of that:

impl Clone for Box<dyn SomeTrait> {
    fn clone(&self) -> Self { self.clone_dyn_boxed() }
}

This does require implementors of SomeTrait to implement clone_dyn_boxed themselves. You could work around this by introducing another trait that's implemented when the implementation is obvious:

trait SomeTrait: SomeTraitClone {}

trait SomeTraitClone {
    fn clone_dyn_boxed(&self) -> Box<dyn SomeTrait>;
}

impl<T: SomeTrait + Clone + 'static> SomeTraitClone for T {
    fn clone_dyn_boxed(&self) -> Box<dyn SomeTrait> {
        Box::new(self.clone())
    }
}
2 Likes

Well it seems like that at least a couple of times I've been close to solving the issue but after your answer I've saw where I was mistaking the previous times.

Thank you for the advice on manual memory management and the solution.

Theoretically speaking I'm still curious whats the proper way of manual copy from heap to heap?

The functions in ptr are the correct way to do manual memory copying, but you'll likely get double-free/use-after-free issues if the data you're copying contains pointers. For example

struct ContainsBox {
    val: Box<i32>
}

// WARNING: UNSAFE & BROKEN do not do this
fn main() {
    unsafe {
        let a = ContainsBox { val: Box::new(3) };
        let b = std::ptr::read(&a);
        println!("{:p}: {}", &*a.val, a.val);
        println!("{:p}: {}", &*b.val, b.val);
        drop(a);
        drop(b); // double-free occurs here
        println!("this is never printed");
    }
}
1 Like

@jethrogb That actually clarified a couple things that I understood wrong in my head, thank you again.

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.