Rust
The poster child for memory safety
Rust's memory safety relies on two main principles: ownership and borrowing.
Every value in Rust has an owner, meaning that at any given point in the code there is one thing that can have read/write control over the value. Ownership can be borrowed temporarily, but this is tracked by Rust's compiler and the violation of any ownership rules does not compile.
This is a different approach to other programming languages. As we have seen so far, C has no such concept of memory safety. Higher-level managed languages, such as Python, Java or C#, handle memory-safety in the runtime. This compromises speed, as well as the requirement of a runtime.
This article has a good explanation, as does this Stack Overflow post. The official documentation for this topic is also fantastic.
Ownership
Consider the following code:
fn main() {
let original = "Hello, World!".to_string();
let other = original;
println!("{}", original);
}While this is valid in many other languages, Rust will throw an error here because the value is moved.
error[E0382]: borrow of moved value: `original`
--> src/main.rs:4:20
|
2 | let original = "Hello, World!".to_string();
| -------- move occurs because `original` has type `String`, which does not implement the `Copy` trait
3 | let other = original;
| -------- value moved here
4 | println!("{}", original);
| ^^^^^^^^ value borrowed here after moveIn the second line, as soon as we define other, it becomes invalid to use original again. This protects against double-frees, use-after-frees and any other sorts of vulnerabilities that can arise due to dangling pointers.
Borrowing
Instead of copying data or transferring ownership, Rust allows you to borrow data. This borrowing can be done immutably or mutably. There are restrictions on borrowing which prevent data races, iterator invalidation and bugs caused by concurrency.
Immutable Borrowing
Immutable borrowing allows for read-only borrows. There can be an any number of these borrows at any one time:
This compiles correctly, and acts as you would expect.
Mutable Borrowing
Mutable borrowing is a read/write borrow.
However, to prevent bugs such as data races, we cannot have two mutable borrows:
Similarly, we actually can't take an immutable reference either!
So we can have either
one mutable borrow
any number of immutable borrows
But it is not allowed for us to mix and match - the compiler enforces this.
Notably, even if the borrow is immutable we are unable to modify the original mutable variable. Take the following example:
We get the error
Coming from a language like C, we can be forgiven for thinking that we can get a pointer to a variable and, once the variable is modified, read the new value with this pointer. But Rust doesn't allow that!
Lifetimes
A lifetime is what the compiler's borrow checker uses to ensure borrows are valid. A lifetime begins when a variable is created, and ends when it is destroyed. The Rust book has a fantastic description of lifetimes, as well as how you can explicitly control them. The idea is that a variable is tied to the existence of other variables, and dies once they die.
Think about this example:
In something like C, x would go out of scope and die once the } ends the scope, while r lives on as a dangling reference to where x would have been. In Rust, the compiler catches this and refuses to compile.
Meanwhile, if we move the println! into the scope defined by the {}, the program compiles fine as x lives long enough for the borrowed value to be used.
No Null Pointers
Rust does not have null pointers - cases must be handled explicitly. This prevents null pointer dereferences and maybe-null bugs.
Out of Bounds
Rust performs out-of-bounds checking at compilation time, but if this is impossible (e.g. the index is provided by the user), it automatically includes runtime OOB checks. Take the following code for example:
If we run the program and input 5, which is OOB, the program panics:
Last updated
Was this helpful?