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
andArc
--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.