Why Rust's Box<T> Clone implementation requires T to be Clone?

I was writing a library for a generic Box<T> and on one part of the code I needed to clone the Box<T>, so I did something like this:

impl<T> OnTheFlySwap<T>
where
    T: ?Sized + Send + Sync, Box<T>: Clone
{ 

I added Box<T>: Clone thinking this is not a big deal, because only objects that explicitly prohibits Box<T>: Clone would have a problem. But no, apparently if the object does not explicitly implements Box<T>: Clone then I have a problem, because this is the official impl Clone for Box:

impl<T, A> Clone for Box<T, A> where
    T: Clone,
    A: Allocator + Clone, 

It requires T to be Clone. Why? Wouldn't every Box<T> be Clone, since cloning a Box requires no time? If an object does not want its box to be clone then it could implement !Clone for it, but the default should be impl Clone for Box<T> for any T.

You are correct that cloning a box requires constant time, however what happens to the value being pointed to? If we just copy the pointer then we've got two Box<T>'s thinking they own the same T, which is a great way to have double-frees.

5 Likes

It's about ownership, not time. Boxes own their contents. If they worked like you suggested, you could clone anything (a Mutex, a &mut, ...) by throwing it in a Box.

1 Like

indeed. I was using Box because it is Send and allows interior mutability. Is there a similar type that does both, but can also hold a dyn Trait? Arc<Mutex<>> could do I guess, but I don't need Sync and don't want to do an unnecessary lock.

Box<T> is only Send if T: Send, and it doesn't allow interior mutability by itself.

If you want a pointer (e.g. Arc) to be Send, then by definition your type has to be Sync, because when you move or clone the pointer across threads, that means you will access the same pointed object from different threads. There is no (safe) way around this. For this reason, for thread-safe interior mutability, you almost always need Arc<Mutex> or Arc<RwLock>.


Note that the only reason Box is Send when T: Send is precisely because it uniquely owns its pointee. A Box<T> behaves almost exactly like a plain T in terms of ownership and borrowing, except for the fact that it contains indirection. But it's not like a raw pointer in C, it's more like a unique_ptr in C++. If it were allowed to be Send and if it were at the same time copiable by just copying the pointer without respect to ownership, then it would need a T: Sync bound in order to implement Send, because then again, you could copy the same pointer into different threads and access the pointee concurrently from there.

5 Likes

what do you mean by it not allowing interior mutability? I thought that if it implemented DerefMut it then allowed interior mutability

Interior mutability is being able to mutate through a shared reference. DerefMut is about the dereference operator (*) and being able to coerce from one exclusive reference to another.

2 Likes

Interior mutability means that you are allowed to mutate a value through an immutable reference. A type implementing DerefMut doesn't do this, since DerefMut requires a mutable reference. Interior mutability is only provided by special wrapper types such as Cell, RefCell, Mutex and RwLock, all of which eventually boil down to the usage of UnsafeCell, Rust's interior mutability primitive.

1 Like

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.