Skip to content

Latest commit

 

History

History
111 lines (81 loc) · 3.02 KB

05_ownership.md

File metadata and controls

111 lines (81 loc) · 3.02 KB

Ownership

Rust's ownership system follows these rules:

  • Each value has a variable that’s called its owner
  • There can only be one owner at a time
  • When the owner goes out of scope, the value will be dropped

Values have to have a fixed size known at compile-time to be stored on the stack.

Variable- or dynamically-sized data are stored on the heap, and their pointer is stored on the stack.

RAII

Rust enforces RAII (Resource Acquisition Is Initialization). In addition to holding data on the stack, variables also own resources allocated on the heap.

Variable bindings have a scope, and are constrained to live in a block. Variables are valid as long as their scope is valid:

fn main() {
    let w = "kek";
    {                      // s is not valid here, it’s not yet declared
        let s = "hello";   // s is valid from this point forward
        // ...
    }                      // this scope is now over, and s is no longer valid
    // w valid, s invalid
}

When a variable goes out of scope, its Drop trait destructor is called and its resources are freed.

Stack

When a primitive value is assigned to another variable or passed to a function, its value is copied and stored in the function's stack. This copy has its own scope in the function:

fn main() {
    let n1 = 5;
    let n2 = n1; // n1 copied to n2, both valid

    gimme_string(n2);
    // both n1 and n2 valid
}

fn gimme_number(n: i32) {  // n copied, has the scope of the function
    println!("Got a number: {}", n);
}                          // nothing special happens

When a function ends, the function's stack data are popped.

Heap

When a dynamic memory is allocated, a fat pointer to this data is stored into a variable.

A fat pointer is a pointer containing additional metadata, like length and capacity.

When this variable goes out of scope, its owned resources are freed:

fn print_kek() {
    let s1 = "hello".to_string();  // s1 points to the allocated data
    println!("{}", s1);
}                                    // s1 goes out of scope, data freed

This is how a String fat pointer looks like:

String pointer

Moving

When a fat pointer is assigned to a variable, the variable becomes the pointer's owner. If the pointer is assigned to another variable or passed to a function, the pointer is moved and gets a new owner:

fn main() {
    let s1 = "hello".to_string();
    let s2 = s1; // s1 moved to s2, s1 is no longer valid

    gimme_string(s2);
    // cannot use s2 anymore :(
}

fn gimme_string(s: String) {  // s moved, now owned by the function
    println!("Now I own: {}", s);
}                             // s no longer valid and deallocated

When returning fat pointers from functions, their ownership is moved:

fn main() {
    let s = make_string(); // s owns the return value of make_string()
}

fn make_string() -> String {
    "lmao".to_string()
}