Static Ownership

Dear Computer

Chapter 11: Managing Memory

Static Ownership

Both tracing and reference counting detect garbage at runtime. Surely runtime detection is a requirement of any garbage collector. There has to be garbage before it can be detected, right? No. Rust detects at compile time when memory will become garbage. It does this by imposing a notion of single ownership on allocated memory.

When a normal variable is bound to a value, that variable becomes its sole owner. In this code, the message variable is the owner of a heap string:

Rust
{
  let message = String::from("You are not alone.");
} // scope closes, and message is automatically freed

A value has exactly one owner. The compiler examines the lifetime of this owner by inspecting (but not running) the source code. When that owner's lifetime ends due to scope changes or reassignment, the compiler inserts a free call in the generated machine code. A Rust program does not experience the unpredictable interruptions of tracing nor does it maintain counts of an object's references. The free calls are all inserted through the compiler's static analysis, not runtime checks.

This collection strategy is viable only because values have a single owner. If multiple owners were allowed, identifying an allocation as garbage would be incredibly complex. But how can a value have only a single owner? How can data be shared with functions if it is owned by the caller? How can objects be added and retrieved from a collection?

Moving

A value has only one owner, but ownership may change hands by moving the value to a different owner. Movement happens in two situations. The assignment operation in this code moves the heap string from its original owner verb to its new owner label:

Rust
let verb = String::from("submit");
let label = verb;

Additionally, passing a value as an actual parameter is by default a move to the formal parameter. In this code, label becomes the new owner of the heap string upon the function call:

Rust
fn main() {
  let verb = String::from("submit");
  show_button(verb);
}

fn show_button(label: String) {
  // ...
}

You've seen statements like label = verb in other languages, in which label becomes just another alias to the source object. For example, this C code makes q an alias of p:

C
int *p = (int *) malloc(sizeof(int) * 50);
int *q = p;

Aliasing is trouble. We might free an object through one alias and assign it NULL, but the other alias sticks around as a dangling pointer.

Moving a value in Rust has different semantics than creating an alias. When a value is moved, the old owner becomes invalid. If we try to do anything with the old owner, the compiler rejects our code. This code, for example, does not compile:

Rust
let verb = String::from("submit");
let label = verb;
println!("{}", verb);  // compile error, verb has no value

The error goes away if we print verb prior to the label assignment. At that point, verb is still the owner.

Borrowing

A consequence of movement is that we lose ownership of a value when we pass it to a function. Imagine if telling someone your phone number meant that they then owned your phone number. You'd probably never share your number. Similarly, we might never want to call a function if it means losing ownership of our variables.

Rust provides a borrowing mechanism so that we can share a value without transferring ownership. To make a function borrow rather than own a parameter, we prefix both the actual parameter expression and the formal parameter type with the reference operator &. In this program, main lends a string to report but retains ownership:

Rust
fn main() {
  let weather = String::from("spiders"); 
  report(&weather);
  weather.clear();      // ok, weather is still an owner
  weather.push_str("clear");
}

fn report(conditions: &String) {
  println!("The weather is {}.", conditions);
}

Borrowed references are implemented via pointers, which are cheap to copy. But references in Rust do not have address semantics. We cannot use them to access memory directly, and we cannot apply arithmetic to move the pointer to neighboring memory cells.

The compiler ensures that no reference outlives its owner. In this code, reference y borrows x and is still active when x gets reassigned:

Rust
let mut x = Vec::<u32>::new();
let y = &x;
x = Vec::<u32>::new();   // compile error, x loses its Vec,
println!("{:?}", y);     //   but y depends on x

The compiler rejects this code because borrowed y outlives the original vector. However, if we flip the order of the last two statements, the code compiles and runs just fine. The lifetime of y would end before x is reassigned.

Mutable References

We cannot change a value through a plain reference, even if the owning variable is mutable. This code fails to compile because it calls the mutating method push_str on immutable reference y:

Rust
let mut x = String::from("ear");
let y = &x;
y.push_str("ring");    // compile error

If a reference is immutable, then the borrowing code can only read and not edit the value. This is a severe limitation. Good news. We create mutable references using the &mut operator. This code changes the reference operator & to the mutable reference operator &mut:

Rust
let mut x = String::from("ear");
let y = &mut x;
y.push_str("ring");    // mutates the string

If we create a mutable reference, no other reference may be active at the same time, not even an immutable one. This code fails to compile because immutable y is still active when mutable z is declared:

Rust
let mut x = String::from("ear");
let y = &x;
let z = &mut x;   // compile error
println!("{}", y);

If the println! call was removed or even moved up before the declaration of z, then y would no longer be considered active when z was declared, and the code would compile.

If we could have two mutable references active at the same time, then there's a chance that simultaneous edits would put the value in an inconsistent state. Some languages allow simultaneous edits and put the burden of synchronizing them on the programmer. Rust's borrow checker prevents simultaneous editors from even existing.

If we could have a mutable reference and an immutable reference active at the same time, then the entity with the immutable reference would be surprised to find the value that it believes immutable has been mutated.

Your past experience with permissive languages will likely get you in trouble with Rust's borrow checker. Stick to these principles to stay in its good graces:

Copy Semantics

Sometimes we might wish to make an independent copy of a value instead of moving it from one owner to another. In fact, this is what happens with primitive data and simple types like tuples made of primitives. This code creates two separate u32 instances and increments them:

Rust
let mut m = 17;
let mut n = m;
m += 1;   // 18
n -= 1;   // 16

Variable m starts off with the value 17, and n gets an independent copy of this value. Changes to one copy are not reflected in the other.

More complex types are moved rather than copied because copying large objects is expensive. But we can force a copy by calling the clone method. This code creates and edits two separate strings, with variable n getting its own copy of the string content of m:

Rust
let mut m = String::from(":");
let mut n = m.clone();
m.push_str(")");       // m is :)
n.push_str("(");       // n is :(

We can ask the compiler to automatically generate clone methods for our custom types using traits, which we'll examine next chapter.

← Reference CountingBox Type →