2022-09-07
Last week I wrote a game of life in Rust with Tyler, and today we tidied it up a bit. I found the concept of ownership a bit tricky so I wanted to write about it here.
Rust's strict ownership system means that it has memory safety and thread safety. It also does that without a garbage collector, which means less computational cost.
Variable bindings have ownership of the value that is bound to them. When the binding goes out of scope, the memory used to store the value is deallocated:
fn main() {
let x = "hello";
} // x goes out of scope here and the memory is deallocated
In Rust there can only be one binding to a value at one time:
fn main() {
let x = "hello";
let y = x; // x goes out of scope here because ownership of the value was moved to y
}
Values that live on the heap (eg. strings and collections) are expensive to copy. So by default Rust doesn't copy them unless we explicitly call clone(). That's why in the example above the value gets moved to a different binding rather than copied.
Values that live on the stack (scalar values - integers, floats, booleans, and characters) are cheap to copy. So instead of transferring ownership, the compiler just makes a copy:
fn main() {
let x = 1;
let y = x; // x and y are both in scope now - there are two value on the stack
}
We could clone values every time we want to pass them, but that would use a lot of memory. Instead we a can pass references to the value - that allows us to keep the value in scope and allow the value to be read elsewhere. A caveat is that the reference only lives as long as the value - so once the value goes out of scope the reference can no longer be used.
We can also pass a mutable reference, which allows the value to be written to elsewhere. While there's a mutable reference in scope, no other reference can be passed.
I wrote examples of some of these here.