We know that Copy
trait is used in Rust to indicate that a type implementing it has a copy semantics. It means that when an object of that type is assigned to another variable or passed as an argument to a function - the full object is implicitly copied. That object (and its copies) would have a size known at compile time and hence it's possible to fully allocate it on the stack.
Primitive times like i32
already implement Copy
:
let a = 1;
let b = a;
When we assign a
to b
, essentially we bind the variable b
to a new implicitly created copy of a
allocated on the stack.
We can implement Copy
for our custom type. One prerequisite for that is that all constituents of our type should also implement Copy
:
#[derive(Copy, Clone)]
struct MyType {
a: i32,
b: i32,
}
The easiest way to implement it is to use derive(Copy, Clone)
attribute that will automatically generate Copy
trait implementation for our type (note that we also have to implement Clone
because it's a supertrait of Copy
). Since all constituents (a
, b
) implement Copy
, the parent type MyType
also could implement it.
Not every type could implement Copy
trait. For example, this would not compile:
#[derive(Copy, Clone)]
struct MyBadType {
a: i32,
b: String, // this field does not implement `Copy`
}
// compilation error: the trait `Copy` may not be implemented for this type
MyBadType
could not implement Copy
because String
doesn't implement Copy
. String
type is one example of a type that could not implement Copy
safely, because of the way how strings are organized in memory:
let s = String::from("abcdef");
The string abcdef
(bound to the variable s
) consists of 2 parts: a small data structure allocated on the stack that points to the actual string content allocated on the heap. That is different from the primitive types like i32
which have only the stack part.
It means that for String
, a simple shallow copying of the stack part only would just lead to having several references to the same object residing on the heap. For example:
let s1 = String::from("abcdef");
let s2 = s1;
Copy
semantics implies doing a simple stack-based copy of objects that could be fully allocated on the stack (e.g. objects of primitive types like i32
). But String
type doesn't comply with this semantics because copying only a stack part (shallow copy) would not copy the actual referenced object allocated on the heap. In the example above, 2 variables (s1
and s2
) now reference the same object abcdef
allocated on the heap.
Also it's not clear now which variable (s1
or s2
) would actually own the object allocated on the heap, and ultimately how Rust should release the memory to avoid doing it twice (first time when s1
goes out of scope and second time when s2
goes out)?
A solution for that in Rust is that a variable of a type that doesn't implement Copy
trait (i.e. a type that doesn't have a simple stack-based copy semantics) is rather moved when assigning it to another variable. Moving means that apart from biding that another variable to a shallow copy of the first variable, the first variable is invalidated (the compiler would not allow us to use it after the move):
let s1 = String::from("abcdef");
let s2 = s1;
println!("{s2}");
//println!("{s1}"); //compilation error: borrow of moved value: `s1`
The string s1
is moved to the variable s2
. In means that s2
is now bound to a shallow copy of s1
, and that s2
takes ownership of the actual string abcdef
stored on the heap. It's no longer possible to use s1
after the move. And since s2
is now the owner, only when s2
variable goes out of scope, the corresponding heap memory will be freed up (unless the ownership was moved to another variable before that).
Similar to a variable assignment, passing a value to a function or returning a value from a function would also transfer ownership. There is a way to avoid transferring ownership by using references and borrowing , which might be explored in the next article. Thanks for reading!