Day 7: Ownership in Rust

Coffee near the laptop closeup. Working from home.

I wasn’t originally going to write about ownership in Rust because honestly
it’s explained better in the rust book.
But then I remembered that these posts are also my notes and memory
so I thought what the heck. Let’s write what I understood here.

Rust doesn’t use a garbage collector to free unused memory and but unlike C/C++ it doesn’t expect you to manage memory. It introduces a new idea that allows it to guarantee safe memory management and this concept is Ownership.

Ownership in Rust
By Rust Foundation, CC BY 4.0 Ownership in Rust

Scope

Before we get to ownership, let’s talk scope. In general, we can define scope as the region where something is valid. So let’s say we have a variable name, where is it valid? That depends on where it’s defined

fn main() {
    let name = "Michael";
    println!("Hello {}", name);
}
Code language: Rust (rust)

The name variable in this context is only valid in the main function.
If we add a block {} and define it inside, then it is only valid inside that block.

fn main() {
    let name1 = "Michael";
    loop {
        let mut count = 0;
        println!("Count {}", count);
        count += 1;
        if count > 10 {
            break;
        }
    }
    // name is still valid as long as we use it.
    //count is not valid
    println!("Hello {}", name)
}
Code language: Rust (rust)

The rules for scope are pretty similar to JavaScript except for one thing. If you notice on line 11 of the previous code I said, if we use it.
In rust, a variable’s scope is further constrained between when it was defined and when it was last used.

This will be important later.

Rust Ownership Rules

  1. In Rust, every piece of data has an owner. The variable name owns the value “Michael”.
  2. There can only ever be ONE owner to a piece of data.
  3. When the owner goes out of scope, the value is dropped.

Now let’s think back to our scope, the value in count is freed after we exit the loop, and the value of name is dropped after the last println.

If we tried to access count we’d get an error, but if we tried to access name we wouldn’t get one as we would have moved the last place it was used.

So what happens when you pass a variable to a function?

fn some_func(value1: String) {
    println!("Value {}", value);
}

fn main() {
    let value = String::from("Hello");
    some_func(value);
    println!("Main Print {}", value);
}
Code language: Rust (rust)

Past programming experience says this should work but we get a compile error. Remember, data should have only one owner, so whose the owner of the data? Turns out, that when we pass values to functions, we transfer ownership as well. This is not just when passing values, it’s also when we make assignments.

let v1 = String::from("Test");
let v2 = v1;
println!("Value {}", v1);
// error
Code language: Rust (rust)

Caveat

If we try the same example above with an Int or Bool or Float, it works.

So what gives?

By default all assignments move value from one variable to another,
ownership included. The only exception is if a data type has the Copy trait. I haven’t gotten to traits yet so I can’t explain further but they are like modifiers that add some functionality to variables, etc.

So it’s enough to remember that integers, floats, boolean, tuples and char data types have the copy trait. For everything else, strings, structs, etc, they like to move it, move it.

King Julian is not amused.
Adrien Sifre King Julian is not amused.

References

So we can modify our example above to

fn some_func(value1: String) -> String {
    println!("Value {}", value1);
    value1
}
Code language: Rust (rust)

And it would return ownership, but we’d have to reassign

let value = String::from("Hello");
let value = some_func(value);
Code language: Rust (rust)

That’s tedious, right? In comes references.

You have a phone, you want that cute girl to put her number into your phone, do you give her ownership of the phone and hope she gives it back? No, you let her borrow the phone, do what she needs to then return it.

In Rust, we can do that by passing a reference instead of passing the variable ownership and all.

fn some_func(value1: &String) {
    println!("Value {}", value1);
}
Code language: Rust (rust)

and call it like

some_func(&value);
Code language: Rust (rust)

What we did is pass a reference to value named value1, Ownership stays with value and value1 is only borrowing the value. By default references are immutable but you can specify mutability as long as the owner variable is also mutable.

let mut value = String::from("Hello");

some_func(&mut value);

...

fn some_func(value1: &mut String){
    ...
}
Code language: Rust (rust)

Mutability Caveat

What happens if we have a mutable reference that’s updating a value at the same time as another mutable reference?

The Rust guys thought about this and there is a limit. You can have as many immutable references as you want. But you can only ever have one mutable reference at a time. And going further if you have a mutable reference you can not have any other kind of reference.

So a million immutable references or just one mutable.

You can’t mix immutable and mutable references, compiler error.

Now, remember when we talked about scopes? Here’s a bit of weirdness:

fn main () {
    let mut s1 = String::from("Hello");
    let s2 = &s1;
    let s3 = &s1;

    println!("Test {}, {}", s2, s3);

    let s4 = &mut s1;
    println!("test 2 {}", s4);
}
Code language: Rust (rust)

Will this work?

Yes, it will, remember scope for variables starts when they are defined and ends at last usage. s1 and s2 are last used at the println! and that’s where their scope ends.

That means after that point we are free to create a mutable reference without issue. If we reference either s1 or s2 after the mutable definition, then we have extended the scope of s1 and s2 and we’d get an error because we’d have mutable and immutable references at the same time.

Conclusion

Whew, that was a long post. But that’s the basics of Ownership. This is the magic that allows Rust to be memory safe. It also appears to be the feature that causes premature baldness in new Rust developers and has its own term associated with it, fighting with the borrow checker.

It’s an important concept to get especially when starting out and is pretty eye-opening. I recommend reading through the Rust Book chapter on Ownership,
it’s worth your time. If I got anything wrong or there’s an error in my examples, those or mine alone, let me know about them on Twitter, @phoexer, happy coding.


Leave a Reply

Your email address will not be published. Required fields are marked *