How to implement copy trait for external crate types?

I'm trying to use Struct gmp::mpz::Mpz for big integers. However, when I try to move a variable between structs, like in the snippet below, it complains about the type has not implemented the Copy trait.

struct A{
   q: Mpz, 

struct B{
   q: Mpz

impl A{
   fn createB(&self) -> B{
       B{q: self.q} 

It says "move occurs because self.q has type gmp::mpz::Mpz, which does not implement the Copy trait".

Is it possible to implement the copy trait myself? Or are there some more preferable ways to make this work in Rust?

Use clone

1 Like

This error occurs because A::createB takes a reference to self (&self); you cannot give ownership of self.q to the new B because self still owns it, and self continues to exist after the method call. One way to deal with this is to write instead

impl A {
    fn createB(self) -> B {
        // ...
        B { q: self.q }

Taking ownership of self allows you to move self.q into the new B. But note that this means if you do

let a = A::new();
let b = a.createB();

you will no longer be able to use a after the second line.

Rust will not allow you to implement an external trait (like Copy, which comes from the standard library) on an external type (like Mpz, which comes from the gmp crate), and in any case that's not what you want here. You should either change the signature of A::createB as described or rewrite it as

fn createB(&self) -> B {
    // ...
    B { q: self.q.clone() }

if gmp::mpz::Mpz implements Clone, which it probably does. (The second option is what @jonh is referring to.)

Thanks so much for the explanation! Just curious: then, when do I ever need to use Copy? Seems clone does the job all the time.

T: Copy (which requires T: Clone) tells the compiler that not only can values of type T be cloned, but cloning them is so cheap that it (the compiler) is free to do it implicitly in circumstances that would otherwise require moving a value. This makes it simpler to work with such values, e.g. since i32: Copy, you can do this:

let x: i32 = 17;
let y: i32 = x;
let z: i32 = x + 1;

even though normally (for non-Copy types, like String) you couldn't use x again after the second line. The standard library documentation is also helpful here:

1 Like

Copy is actually more than just cheap, it is trivial, i.e. it must not contain any non-Copy types, which is what allows certain access patterns to be sound¹ and zero-cost in a generic context, when doing the same with only a Clone type would not. The prime example is std::cell::Cell.

In particular the method get, which is only implemented for Copy types, which allows you to take a look at the interior value without borrowing, simply by copying it. The other notable (and still unstable) method on Cell is update. Its goal is to update the interior value, which would usually require a mutable borrow, but Cell cannot soundly hand out mutable borrows (or any kind of borrow). Therefore it limits the implementation of update to Copy types and works by getting the interior value, moving it into the closure and having the closure return a value of the same type, which the interior value is set to, afterwards.

This exploitation of Copy types is what allows Cell to be zero-cost and avoid the additional bookkeeping, that's used for RefCell, which does hand out borrows, but has to track the count at runtime, which results in space and time overhead at runtime.

¹ a safe API is sound, if for any given input in a sound context, it doesn't behave undefined. Undefined behavior is the violation of guarantees the user of a programming language made to the compiler to allow certain types of optimizations, that would otherwise not be possible, e.g. removing runtime bounds checks on every element access when iterating over a vector.

P.S.: Cell does have alternative get methods for non-Copy types, too (take, swap, replace) but they're not guaranteed to be zero-cost.

1 Like

Copy can be considered trivial because it’s the same cost as moving a value; the only difference is whether the original memory location is still considered valid after the move. Cell requires its contents to always be valid, so it only supports swap operations for non-Copy types.

Cell::swap and replace require two memory copies instead of get’s one, but are otherwise zero-cost— One to copy the current value to the result and another to copy its replacement into the Cell. These copies are only of the stack data of the type: anything stored on the heap or otherwise by reference isn’t touched.

Often, though, you’ll either clone a value before storing it or construct an object you don’t care about to take the place of the old value in the Cell, and these are the operations that can potentially be expensive. Cell::take, similarly, is roughly the same cost as Default::default().

1 Like

To elaborate a bit more, the costs aren't hidden in the replace/swap operations themselves, but depend on what has to be done prior to calling them. It can be cheap, but it doesn't have to. If your only goal is to take a look at the value, but let's say you store a Box<T> inside the Cell, then in order to get the value out of the Cell you have to create a new Box.

Default doesn't have any requirements about the cost of construction when calling default, so it could also lead to an allocation on the heap when calling take on a type that requires heap allocation.

Since Copy types always boil down to a move operation, Cell does not incur additional costs for them.

1 Like

Thanks, this was enlightening. (I'm still working on understanding interior mutability; your discussion of Cell here fills in a piece of the puzzle.)

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.