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.
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
- In Rust, every piece of data has an owner. The variable
name
owns the value “Michael”. - There can only ever be ONE owner to a piece of data.
- 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.
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.