DEV Community

Cover image for Rust Reference and Ownership
Daniel
Daniel

Posted on

Rust Reference and Ownership

Rust's system of references and ownership is fundamental to its guarantee of memory safety and concurrency.

Ownership

In rust a piece of data can have only one owner at a time. When a binding goes out of scope, Rust will free the bound resources. Here are some 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. Here is an example:
fn main() {
    let s1 = String::from("hello");
    let s2 = s1; // s1 is moved to s2, s1 is no longer valid

    // println!("{}", s1); // This would cause a compile-time error
    println!("{}", s2);
}

Enter fullscreen mode Exit fullscreen mode

The Stack and the Heap

Both the stack and the heap are parts of memory available to your code to use at runtime, but they are structured in different ways.
The stack stores values in the order it gets them and removes the values in the opposite order. This is referred to as last in, first out. All data stored on the stack must have a known, fixed size. Data with an unknown size at compile time or a size that might change must be stored on the heap instead.
Here is an example of data stored in a stack:

fn main() {
    let x = 5; // Integer is stored on the stack
    let y = x; // Copy of the value, both x and y are on the stack
    println!("x = {}, y = {}", x, y);
}

Enter fullscreen mode Exit fullscreen mode

The heap is less organized: 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.
Here is an example of data stored in a heap:

fn main() {
    let s = String::from("hello"); // String is stored on the heap
    let t = s; // Ownership is moved, t now owns the heap data
    // println!("{}", s); // This would cause a compile-time error
    println!("{}", t);
}
Enter fullscreen mode Exit fullscreen mode

References

References allow you to refer to some value without taking ownership of it. They are indicated by the & symbol.

Borrowing:

Borrowing allows you to have references to data without taking ownership. There are two types of borrowing: immutable and mutable.

Immutable References:

Multiple immutable references are allowed but you cannot have a mutable reference while immutable ones are in use.

fn main() {
    // Create a String instance and bind it to the variable s
    let s = String::from("hello");

    // Pass an immutable reference to s to the calculate_length function
    let len = calculate_length(&s);

    // Print the length of the string using the original string and the length calculated
    println!("The length of '{}' is {}.", s, len);
}

// Define a function that takes an immutable reference to a String and returns its length
fn calculate_length(s: &String) -> usize {
    // Call the len method on the immutable reference to get the length of the string
    s.len()
}

Enter fullscreen mode Exit fullscreen mode
Mutable References:

Only one mutable reference is allowed at a time.

fn main() {
    // Create a mutable String instance and bind it to the variable s
    let mut s = String::from("hello");

    // Pass a mutable reference to s to the change function
    change(&mut s);

    // Print the modified string
    println!("{}", s);
}

// Define a function that takes a mutable reference to a String 
fn change(some_string: &mut String) {
    // Append ", world" to the String referred to by the mutable reference
    some_string.push_str(", world");
}

Enter fullscreen mode Exit fullscreen mode

There are very important rules regarding borrowing.

  1. One piece of data can be borrowed either as a shared borrow or as a mutable borrow at a given time. But not at the same time.
  2. Borrowing applies for both copy types and move types.

Top comments (0)