Its time to introduce The Borrow Checker

The meme's are real, but the borrow checker isn't that hard if you have the right foundation.


  • we have already talked about every thing is on the stack with ptr to the heap if needed

(quick example)

















Types of values

(code time)

value

reference

mutable reference

















Terminology

Dropped - releasing memory



There are THREE rules you must have in your head at all times.

  1. There can only be one value owner
  2. There can be unlimited immutable borrows (reference) with no mutable references
  3. There can be only one mutable reference and no immutable references


There is one rule for Lifetimes

  1. A reference cannot outlive its value


Stated differently

One var owns the the data

One var can change the data

Many vars can look at the data

You cannot look and change the data simultaneously

You cannot refer to something that has been dropped (released in memory)

















Test

  1. create a new struct Item, derives Debug, with one property, count, that is an usize.
  2. create a new fn, add_one that takes in an Item and adds 1 to it.
  3. In the main function, create item of type Item
  4. Print out item (with Debug print)
  5. pass item to add_one.
  6. Print out item again.

Derives Debug

allows for printing of the struct.

#[derive(Debug)]
struct Item {...}

fn main() {
    let item = Item { .. };
    // --------v debug print
    println!("{:?}", item);
    // Item {
    //    count: 69
    // }
}
















First, what is causing the error?

What is causing the error?

#[derive(Debug)]
struct Item {
    count: isize,
}

fn add_one(mut item: Item) {
    item.count += 1;
}

fn main() {
    let item = Item { count: 0 };
    println!("item {:?}", item);
    add_one(item);
    println!("item {:?}", item); // <--- why does this error
}

Let me ask you this question, who owns Item on this line?

let item = Item { count: 0 };

What about this line?

add_one(item);

Did you read the errors from your LSP?

Lets do a quick whiteboard memory layout

















What rule are we breaking?

  1. There can only be one value owner
  2. There can be unlimited immutable borrows (reference) with no mutable references
  3. There can be only one mutable reference and no immutable references
















How do we fix this?

(write code now)

















Complete code


#[derive(Debug)]
struct Item {
    count: isize,
}

fn add_one(item: &mut Item) {
    item.count += 1;
}

fn main() {
    let mut item = Item { count: 0 };
    println!("item {:?}", item);
    add_one(&mut item);
    println!("item {:?}", item);
}
















Lets make the borrow checker angry again!

To do this,

  • create a function called print_all that takes in an immutable borrow (reference) to items and prints each item, one at a time

In the main function

  • create a vector of Items called items
  • grab a mutable reference to item 0 (get_mut)
  • print item 0
  • call print_all
  • print item 0 (you have to code this all)
















So how did we break it?

Try to explain why this happened, then what rule did we break?

















Complete Code

#[derive(Debug)]
struct Item {
    count: isize,
}

fn add_one(item: &mut Item) {
    item.count += 1;
}

fn print_all(items: &Vec<Item>) {
    for item in items {
        println!("print {:?}", item);
    }
}

fn main() {

    let mut items = vec![
        Item { count: 0 },
        Item { count: 0 },
        Item { count: 0 },
    ];

    let item = items.get_mut(0).unwrap();
    add_one(item);
    print_all(&items);

    println!("help {:?}", item);
}
















One more time

  • get a mutable reference named one, get_mut(0)
  • get a mutable reference named two, get_mut(1)
  • println!("{:?}", one)
















What rule are we breaking?

















Complete Code


#[derive(Debug)]
struct Item {
    count: isize,
}

fn main() {

    let mut items = vec![
        Item { count: 0 },
        Item { count: 0 },
        Item { count: 0 },
    ];

    let item = items.get_mut(0).unwrap();
    let item2 = items.get_mut(1).unwrap();
    println!("help {:?}", item);
}
















does this error?

#[derive(Debug)]
struct Item {
    count: isize,
}

fn main() {

    let mut items = vec![
        Item { count: 0 },
        Item { count: 0 },
        Item { count: 0 },
    ];

    let item = items.get_mut(0).unwrap();
    let item2 = items.get_mut(1).unwrap();
    println!("help {:?}", item2);
}

Why or why not?

















Now, with all of your knowledge why does this error?

fn main() {
    let items = vec![1, 2, 3]
        .iter()
        .map(|x| x + 1);

    println!("{:?}", items);
}

lets talk about why this happens!

















Quick Recap: The big three rules

There can only be one value owner

let item = Item { age: 10 };
let other = item; // value moved here

println!("{:?}", item.age); // borrow of moved value (item moved to other)




There can be 0 mutable borrows when there are 1 or more immutable borrows

let mut items = vec![Item { age: 1 }, Item { age: 2 }];

let items2: &Vec<Item> = &items; // immutable borrow occurs here
let items3: &mut Vec<Item> = &mut items; // cannot borrow mutably with
                                         // immutable references out

items2.get(0); // item3 is mutably borrowed




There can only be 1 mutable borrow

let mut items = vec![Item { age: 1 }, Item { age: 2 }];

let items2: &mut Vec<Item> = &mut items; // First mutable borrow
let items3: &mut Vec<Item> = &mut items; // Error occurs here

items2.push(Item { age: 3 }); // nope!
















Applications of the rules

There is a "flow" to references

Since items2 was not used when items3 mutable borrow out, this is ok

let mut items = vec![Item { age: 1 }, Item { age: 2 }];

let items2: &mut Vec<Item> = &mut items; // First mutable borrow
items2.push(Item { age: 3 }); // ok!

let items3: &mut Vec<Item> = &mut items; // Second mutable borrow
items3.push(Item { age: 3 }); // still ok!




References cannot outlive their associated values

let y: &usize;
{
    let x: usize = 5;
    y = &x;
}

println!("ooh no! {}", y);
















Test

Remember this code from the enum section?

struct Custom {
    name: String,
    age: usize,
}

enum Item {
    Number(usize),
    Custom(Custom),
    String(String),
}

fn main() {
    let foo = Item::Number(5);

    match &foo {
        Item::Number(num) => println!("i am a number: {}", num),
        Item::String(str) => println!("i am a string: {}", str),
        Item::Custom(custom) =>
            println!("name: {}, age: {}", custom.name, custom.age),
    }

    match &foo {
        Item::Custom(custom) =>
            println!("name: {}, age: {}", custom.name, custom.age),
        _ => {}
    }

    // ...
}

What rule would we break if we changed the match statements from &foo to foo?

















Questions

I am sure you have a question or two... i can borrow