Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does Rust guarantee memory safety and prevent segfaults?

I was looking for a language to learn, and I saw that Rust is getting quite popular.

Two things impressed me about Rust, memory safety and preventing segfaults.

How does Rust achieve this? What differences between Rust and Java for example enable Rust's safety features?

like image 563
Myra Avatar asked Mar 21 '16 16:03

Myra


People also ask

How is memory managed in Rust?

Rust's memory management model uses a concept called "ownership", where a given object in memory can only be handled by a single variable at a time, and the programmer must be explicit about how ownership is held and transferred.

Why is memory safety important?

Because languages that are not memory safe tend to allow for more bugs and crashes, application stability can be greatly impacted. Even when crashes are not security sensitive they are still a very poor experience for users. Worse, these bugs can be incredibly difficult for developers to track down.

Can safe Rust segfault?

NULL pointer can cause segfault in both C and Rust. This is unsafe code, which is fair enough since the OP didn't specify but he probably means is a segfault possible in safe rust code.

How is Rust safer than C?

Raw numbers. Rust doesn't have any special feature that makes it fast and different from C and/or C++. It is much safer than C++ because of protection mechanisms it follows which, in principle, are also doable in C++ (using std::unique_ptr and std::shared_ptr ).


1 Answers

How Rust achieves memory safety is, at its core, actually quite simple. It hinges mainly on two principles: ownership and borrowing.

Ownership

The compiler uses an affine type system to track the ownership of each value: a value can only be used at most once, after which the compiler refuses to use it again.

fn main() {
    let original = "Hello, World!".to_string();
    let other = original;
    println!("{}", original);
}

yields an error:

error[E0382]: use of moved value: `original`
 --> src/main.rs:4:20
  |
3 |     let other = original;
  |         ----- value moved here
4 |     println!("{}", original);
  |                    ^^^^^^^^ value used here after move
  |
  = note: move occurs because `original` has type `std::string::String`, which does not implement the `Copy` trait

This, notably, prevents the dreaded double-free regularly encountered in C or C++ (prior to smart pointers).

Borrowing

The illumination that comes from Rust is that memory issues occur when one mixes aliasing and mutability: that is, when a single piece of memory is accessible through multiple paths and it is mutated (or moved away) leaving behind dangling pointers.

The core tenet of borrow checking is therefore: Mutability XOR Aliasing. It's similar to a Read-Write Lock, in principle.

This means that the Rust compiler tracks aliasing information, for which it uses the lifetime annotations (those 'a in &'a var) to connect the lifetime of references and the value they refer to together.

A value is borrowed if someone has a reference to it or INTO it (for example, a reference to a field of a struct or to an element of a collection). A borrowed value cannot be moved.

Mutability (without aliasing)

You can obtain only a single mutable reference (&mut T) into a given value at any time, and no immutable reference into this value may exist at the same time; it guarantees that you have exclusive access to this tidbit of memory and thus you can safely mutate it.

Aliasing (without mutability)

You can obtain multiple immutable references (&T) into a given value at any time. However you cannot mutate anything through those references (*).

(*) I am lying; there are structs like RefCell which implement "interior mutability"; they do respect the Mutability XOR Aliasing principle, but defer the check to run-time instead.

That's it?

Nearly so ;)

It is already quite complicated to implement for the compiler-writers, and may unduly constrain the users (some programs that would be safe cannot be proven safe using this system, requiring to jump through hoops), however the core principles are really that simple.

So what's left?

Bounds-checking. It is not rocket-science, but may induce a performance penalty though. Most languages have some degree of support for it, C being the big exception, and C++ having some support for it, although it is optional.

like image 135
Matthieu M. Avatar answered Oct 11 '22 20:10

Matthieu M.