Personally, what makes Rust so fun and intriguing as a beginner to the language and systems programming, is the way it handles memory management.
This is a short post on Rust's memory management system, and hopefully it'll make you a little more interested in Rust.
Wait, what is Rust for?
Some use-cases for Rust include
- dev tools
- operating systems
- databases/storage engines
It gives you the speed and low-level control of C++, with stronger safety checks around memory.
A Quick Rundown on Ownership
Rust has three fundamental ownership rules, which govern how memory is managed
- A value must have an owner
- A value must only have one owner
- When the owner goes out of scope, its value is dropped
fn main() {
let p = 1;
let q = p; // owner is now q
} // q out of scope, value dropped
Single ownership makes the responsibility of freeing memory very clear, since two owners trying to free the same memory allocation is unsafe.
Automatically dropping values after they go out of scope also prevents accidental memory leaks.
Borrowing instead of Owning
References allow callers to borrow data without taking ownership.
Note that a borrow can end at its last use, instead of adhering to lexical scope. Keep this concept in mind when reference scopes are mentioned.
1. Multiple immutable refs can exist for a value
There are no problems doing multiple read-only references.
2. Only one mutable ref can exist for a value in a scope
fn main() {
let mut a = String::from("hello");
let b = &mut a;
let c = &mut a; // compiler throws
println!("{} {}", b, c);
}
Multiple writes to the same value at the same time can cause data races and aliasing bugs. Thus Rust only allows one mutable reference at a time.
3. Mutable and immutable refs can't exist for the same value in the same scope
fn main() {
let mut x = String::from("world");
let y = &x;
let z = &mut x; // compiler throws
println!("{} {}", y, z);
}
If a value is being read through an immutable ref, Rust prevents that same value from being mutated at the same time.
This avoids aliasing bugs where one part of the code assumes a value is stable, while another part changes it underneath.
4. Refs must always point to valid data
fn main() {
let r;
{
let g = 2;
r = &g; // compiler throws, since r outlives g
}
println!("{}", r);
}
Prevents dangling pointers, as the compiler checks for refs that outlive its owner.
Conclusion
To sum it all up, we went through the basics of memory management in Rust, and we saw how these simple yet opinionated rules can help mitigate common memory bugs!
If you want to learn more about Rust, The Book and Rustlings are a great way to start!