Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Ownership

Variable scope

fn main() {
        {                      // s is not valid here, since it's not yet declared
        let s = "hello";   // s is valid from this point forward

        // do stuff with s

    }                      // this scope is now over, and s is no longer valid

}

Ownership rules

  • Each value in Rust has an owner.
  • There can only be one owner at a time.
  • When the owner goes out of scope, the value will be dropped.

String type

  • Unlike string literals (&str), String can grow so its size is not known at compile time.
  • The String contents are stored on the heap. The memory allocator finds space on the heap and returns a pointer to it.
  • The String value itself (pointer + length + capacity) is fixed-size and stored on the stack (i.e. stack representaion of a String which owns the bytes on the heap)
  • When a String goes out of scope, Rust automatically calls drop to free the heap memory
  • See Figure 4-1 in the book.
fn main() {
    {
        let s = String::from("hello"); // s is valid from this point forward

        // do stuff with s
    }   // this scope is now over, and s is no longer valid
}

Move

  • A String assignment copies the stack data (pointer, length, capacity).
  • The heap data is not copied (stay where they are).
  • The original variable s1 becomes invalid and can no longer be used.
  • Ownership of the value moves to the new variable s2. This prevents double-free errors when values are dropped.
  • See Figure 4-4 in the book.
fn main() {
    let s1 = String::from("hello");

    // assigning s1 to s2
    let s2 = s1;  

    // s1 is invalidated and cannot be used here
}

Clone

  • clone performs a deep copy.
  • A new heap allocation is created and the data is copied.
  • Both variables own separate values.
  • Each value is dropped independently.
    let s1 = String::from("hello");

    // assigning s1 to s2 with clone
    let s2 = s1.clone();

    println!("s1 = {s1}, s2 = {s2}");

New value

  • When assigning a new value, the old value stored in the variable is dropped immediately.
  • The binding s stays in scope, but now owns the new value.
  • The new String allocates its own heap memory.
  • See Figure 4-5 in the book.
    let mut s = String::from("hello");

    // assigning new value to a variable
    s = String::from("ahoy");

    println!("{s}, world!");

Stack-only data

  • Types that implement the Copy trait are copied instead of moved.
  • Assignment creates a copy, so both variables remain valid.
  • These types are usually small, fixed-size values that do not require memory cleanup.
  • Examples:
    • integers
    • booleans
    • floating-point numbers
    • char
    • tuples containing only Copy types
    let x = 5;

    // assigning x to y
    let y = x;
    // x is not invalidated
    println!("x = {x}, y = {y}");

Ownership and functions

  • Passing a value to a function transfers ownership unless the type implements Copy.
  • Values that own heap data are dropped when their owner goes out of scope unless the ownership is transferred.
  • Ownership can be transferred into and out of functions through parameters and return values.

Passing value to a function

  • String is moved into the function.
  • i32 is copied because it implements Copy.
fn main() {
    let s = String::from("hello");  // s comes into scope

    takes_ownership(s);             // s's value moves into the function...
                                    // ... and so is no longer valid here

    let x = 5;                      // x comes into scope

    makes_copy(x);                  // Because i32 implements the Copy trait,
                                    // x does NOT move into the function,
                                    // so it's okay to use x afterward.

} // Here, x goes out of scope, then s. However, because s's value was moved,
  // nothing special happens.

fn takes_ownership(some_string: String) { // some_string comes into scope
    println!("{some_string}");
} // Here, some_string goes out of scope and `drop` is called. The backing
  // memory is freed.

fn makes_copy(some_integer: i32) { // some_integer comes into scope
    println!("{some_integer}");
} // Here, some_integer goes out of scope. Nothing special happens.

Return Value

  • Returning a value moves ownership to the caller.
  • Function parameters and return values use the same move rules as assignments.
fn main() {
    let s1 = gives_ownership();        // gives_ownership moves its return
                                       // value into s1

    let s2 = String::from("hello");    // s2 comes into scope

    let s3 = takes_and_gives_back(s2); // s2 is moved into
                                       // takes_and_gives_back, which also
                                       // moves its return value into s3
} // Here, s3 goes out of scope and is dropped. s2 was moved, so nothing
  // happens. s1 goes out of scope and is dropped.

fn gives_ownership() -> String {       // gives_ownership will move its
                                       // return value into the function
                                       // that calls it

    let some_string = String::from("yours"); // some_string comes into scope

    some_string                        // some_string is returned and
                                       // moves out to the calling
                                       // function
}

// This function takes a String and returns a String.
fn takes_and_gives_back(a_string: String) -> String {
    // a_string comes into
    // scope

    a_string  // a_string is returned and moves out to the calling function
}