Memory Safety

Languages like Rust and Swift claim to be "memory-safe". What does that mean?

Rust

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.

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 move

In 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.

Note that the error talks about how the type String does not implement the Copy trait. If a type does implement this, then an assignment like the second line here would duplicate the variable, copying all of the bits over. This wouldn't cause an issue, as there are two distinct variables owning their respective values. What Rust does not want is two variables refering to the same data in memory, which would occur for String types as they are just pointers to the actual string in memory.

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.

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.

Resource Acquisition is Initialization

RAII ensures that whenever an object goes out of scope, its destructor is called and its owned resources are freed. This means you never have to manually free memory and protects you against resource leaks (like memory leaks).

Modern C++ actually supports RAII, which is part of a drive to improve C++ memory safety. Rust requires it, however!

Last updated

Was this helpful?