go4fun.fun
Go4Fun - Fun Interview Puzzles

Follow

Go4Fun - Fun Interview Puzzles

Follow
Rust: Copy trait and ownership

Rust: Copy trait and ownership

go4fun.fun's photo
go4fun.fun
·Nov 24, 2022·

4 min read

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 Copytrait 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");

image1.png

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;

image1.png

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!

 
Share this