2 min read

Rust Book - Chapter 4

Understanding Ownership
💡
These are my personal learning notes as I study

Ownership

A set of rules in Rust for managing memory. Some programming languages explicitly allocate and free memory, others use a garbage collector to clean up unused memory (e.g Java), Rust uses a set of ownership rules that are checked by the compiler. If any of the rules are violated, the program won't compile.

Ownership Rules

  • Every value has an owner
  • There can only be one owner at a time
  • When the owner goes out of scope, the value is dropped

Stack and the Heap

The way memory management works in the stack and heap is key to understanding ownership in Rust. For stack, think of a pile of plates. It works in LIFO manner. Also, data in the stack has to be of known, fixed size. Data with an unknown size at compile time or size that might change must be stored on the heap instead.

The Heap

When you put data on the heap, you request a certain amount of space. The memory allocator finds an empty spot in the heap that is big enough, marks it as being in use, and returns a pointer, which is the address of that location - a process called allocating. Because the pointer to the heap is a known, fixed size, you can store the pointer on the stack, but when you want the actual data, you must follow the pointer.

  • The stack is faster: pushing to the stack is faster than allocating on the heap, because the allocator never has to search for a place to store new data; the location is always at the top of the stack. Allocating space on the heap requires more work because the allocator must find a big enough space to hold the data and then perform bookkeeping to prepare for the next allocation.
  • Data access on the heap is slower: because you have to follow a pointer to get there. A processor can do its job better if it works on data that's close to other data (as it is on the stack) rather than far away (as it can be on the heap).
  • function values are pushed onto the stack: values passed into a function (including, potentially, pointers to data on the heap), local variables get pushed onto the stack. When the function is over, those values get popped off the stack.

The problem Ownership addresses: The purpose of ownership is to manage data on the heap - Keeping track of what parts of code are using what data on the heap, minimizing the amount of duplicate data on the heap, and cleaning up unused data on the heap so you don't run out of space.

    let s1 = String::from("hello");
    let s2 = s1;
    println!("{}", s1);

We'd get a compiler error below. As with other programming languages, a shallow copy [1] is similar here, except that rust would also invalidate the first variable. This is called a move

error[E0382]: borrow of moved value: `s1`
 --> src/main.rs:4:20
  |
2 |     let s1 = String::from("hello");
  |         -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait
3 |     let s2 = s1;
  |              -- value moved here
4 |     println!("{}", s1);
  |                    ^^ value borrowed here after move
  |
  = note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider cloning the value if the performance cost is acceptable
  |
3 |     let s2 = s1.clone();

  1. shallow copy, the concept of copying a pointer, length and capacity, without copying the data. ↩︎