Javier

Rust notes - Ownership and borrowing

Hi there! welcome back, this time we will be back to the notes posts revisiting some of the core concepts of Rust. We will focus on the concepts of ownership and borrowing. These are developed in chapter 4 of the book, but we will also use other sources to internalize each one.

Ownership

Ownership is Rust’s most unique feature, and it enables Rust to make memory safety guarantees without needing a garbage collector.

In Rust memory is managed through a system of ownership with a set of rules that the compiler checks at compile time. None of the ownership features slow down your program while it’s running.

Ownership Rules

  • Each value in Rust has a variable binding 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.

Let's see a couple of examples that introduce the next topic (semantics)

This works as expected

fn main() {
    let a = 1;
    let b = a; // Copy semantics

    println!("{},{}", a, b);
}

But this don't compile

fn main() {
    let a = String::from("str example");
    let b = a; // Move semantics

    println!("{},{}", a, b);
}

ownership error

Move and Copy semantics

  • Move semantics

In the above examples, the second one don't compile. That is because Rust by default have move semantics, that means:

When assigning a variable binding to another variable binding or when passing it to a function(without referencing), the bound resources are moved to the new variable binding and we can not access the original variable binding anymore.

Also, the ownership state of the original bindings is set to moved state.

  • Copy semantics

But, the other example compiles... That is because the type we use implements the Copy trait. ( has copy semantic )

When assigning a variable binding to another variable binding or when passing it to a function(without referencing), the bound resources are made a copy and assign or pass it to the function.

Also, the ownership state of the original bindings is set to copied state. Mostly primitive types implement Copy.

References and Borrowing

Remember the first rule of ownership, a value can have only one owner (a variable binding ). So, what happens when we need to pass the binding to another function or assign them to other variable bindings. In those cases, we are referencing the original binding, borrow the data of it.

Borrowing rules:

  • Either have multiple immutable (&T) borrows
  • OR exclusively one mutable (&mut T) borrow

Looking resources for this post I found this post where it's recommended to use other naming convention that is more clear to me:

Shared references ( &T )

A shared reference means that other references to the same value might exist, possibly on other threads (if T implements Sync) or the caller's stack frame on the current thread.

fn main() {
    let a = vec![1, 2, 3];
    let b = get_first_element(&a);

    println!("{:?} {}", a, b); // [1, 2, 3] 1
}

fn get_first_element(a: &[i32]) -> i32 {
    a[0]
}

Exclusive references ( &mut T)

An exclusive reference means that no other reference to the same value could possibly exist at the same time.

fn main() {
    let mut a = vec![1, 2, 3];
    let b = change_and_get_first_element(&mut a);

    println!("{:?} {}", a, b); // [4, 2, 3] 4
}

fn change_and_get_first_element(a: &mut [i32]) -> i32 {
    a[0] = 4;
    a[0]
}

** Examples based from learning rust


That’s all for today, we revisited the concepts of Ownership and Borrowing, in the next note we will talking about Lifetimes.

As always, I write this as a learning journal and any feedback is welcome.

Thanks!