Ownership

In Rust, every value has a single owner that determines its lifetime. When the owner is freed--aka dropped--the owner of the value is dropped too.

A variables owns its value. When control leaves the block in which the variable is declared, the variable is dropped.

Owners and their owned values form trees. Every value in a Rust program is a member of some tree, rooted in some variable.

In their purest form, Rust's ownership model is too rigid to be usable. But, the language provides several mechanisms to make it work:

  • Values can be moved from one owner to another
  • The std library provides reference-counted pointer types--Rc and Arc--which allows a value to have multiple owners, with some restrictions
  • References can be borrowe from values; references are non-owning pointers with limited lifetimes

Moves

For values of most types, operations like assignment to variables, passing to functions, or returning from functions don't copy values: they move it.

In Rust, assignments of most types move the value from the source to the destination, leaving the source uninitialized.


#![allow(unused)]
fn main() {
let name_1 = vec!["alex", "eden"];
let name_2 = name_1;        // moves name_1's heap memory to name_2
println!("{:?}", name_1);   // fails - name_1 has become uninitialized
}

For the above to work, we have to explicity ask for copies of the values using .clone(), which is built into most types.


#![allow(unused)]
fn main() {
let name_1 = vec!["alex", "eden"];
let name_2 = name_1.clone();
println!("{:?}", name_1);
}

More Operations that Move

If you move a value into a variable (via assignment) that was already initialized, Rust drops the variable's prior value.

Passing arguments to functions moves ownership to the function's parameters; returning a value from a function move ownership to the caller.

Building a tuple moves ownership of the values into the tuple structure itself. The same applies to other complex types.

Keep in mind that transfer of ownership does not imply a change in the owned heap storage. Moves apply to the value proper, which for types like vectors and strings, are the three-word header stored on the stack that represents the variable.

Moves and Control Flow

As a general principle, if it's possible for a variable to have had its value moved away, and it hasn't definitely been given a new value since, it's considered uninitialized.

Moves and Indexed Content

Copy Types: The Exception to Moves

In general, most types are moved. The exception are types that implement the Copy trait. In these cases, the value is copied, rather than moved. This applies to all types of moves, including passing Copy types to functions and constructors.

The standard Copy types include all the machine integers and floating-point numeric types, the char and bool types, and a few others. A tuple or fixed-size array of Copy types is itself a Copy type.

As a rule of thumb, any type that needs to do something special when a value is dropped cannot Copy. Vectors, files, mutexes, etc. cannot be Copy typed.

By default, struct and enum types are not Copy, but they can be, if their fields are themselves Copy.

To make a type Copy, add the attribute #[derive(Copy, Clone)] above its definition.

In Rust, every move is a byte-for-byte, shallow copy that leaves the source uninitialized. Copies are the same, except that the source remains initialized.

Rc and Arc: Shared Ownership

Arc stands for atomic reference count. Rc stands for reference count.

The difference between Arc and Rc is that an Arc is safe to share between threads directly, whereas a Rc uses faster non-thread-safe code to update its reference count.

If you don't need to share pointers between threads, use Rc, rather than suffer the performance penalty of using Arc.

For any type T, an Rc<T> value is a pointer to a heap-allocated T that has had a reference count affixed to it. Cloning an Rc<T> value does not copy the T; rather, is creates another pointer to it and increments the reference count.

A value owned by an Rc pointer is immutable.

The main risk with using Rc pointers to manage memory is that if there are ever two Rc values to point to each other, each will keep the other's reference count always above 0, and neither will ever be freed. This is called a reference cycle.

Example of a reference cycle

The workaround for avoiding reference cycles is using a language mechanism called interior mutability.