guaranteeing memory safety in rust

53
Rust Nicholas Matsakis Mozilla Research

Upload: nikomatsakis

Post on 20-Jun-2015

1.508 views

Category:

Software


1 download

DESCRIPTION

An overview of how Rust guarantees memory safety.

TRANSCRIPT

Page 1: Guaranteeing Memory Safety in Rust

Rust

Nicholas Matsakis Mozilla Research

Page 2: Guaranteeing Memory Safety in Rust

What makes Rust different?

C++

More Control More Safety

Haske

llJa

va ML

Rust: Control and safety

Page 3: Guaranteeing Memory Safety in Rust

Why Mozilla?

Browsers need control.

Browsers need safety.

Servo: Next-generation browser built in Rust.

Page 4: Guaranteeing Memory Safety in Rust

What is control?void example() { vector<string> vector; … auto& elem = vector[0]; … }

string[0]

…elem

vector

data

length

capacity

[0]

[n]

[…]

‘H’

‘e’

Stack and inline layout.

Lightweight references

Deterministic destruction

Stack Heap

C++

Page 5: Guaranteeing Memory Safety in Rust

Zero-cost abstraction

Ability to define abstractions that optimize away to nothing.

vector data

length

cap.

[0]

[…]

data cap.

‘H’

‘e’

[…]Not just memory layout: - Static dispatch - Template expansion - … Java

Page 6: Guaranteeing Memory Safety in Rust

What is safety?void example() { vector<string> vector; … auto& elem = vector[0]; vector.push_back(some_string); cout << elem; }

vector

data

length

capacity

[0]

[0]

[1]

elemAliasing: more than one pointer to same memory.

Dangling pointer: pointer to freed memory.

C++

Mutating the vector freed old contents.

Page 7: Guaranteeing Memory Safety in Rust

What about GC?

No control.

Requires a runtime.

Insufficient to prevent related problems: iterator invalidation, data races.

Page 8: Guaranteeing Memory Safety in Rust

void foo(Foo &&f, …) { vec.push_back(f); }

Gut it:

void foo(const Foo &f, …) { use(f.bar); }

Read it:

void foo(Foo &f, …) { f.bar = …; }

Write it:

void foo(unique_ptr<Foo> f, …) { … }

Keep it:

C++

Page 9: Guaranteeing Memory Safety in Rust

Definitely progress. !But definitely not safe: !- Iterator invalidation. - Double moves. - Pointers into stack or freed memory. - Data races. - … all that stuff I talked about before … !Ultimately, C++ mechanisms are unenforced.

Page 10: Guaranteeing Memory Safety in Rust

The Rust Solution

Codify and enforce safe patterns using the type system: !1. Always have a clear owner. 2. While iterating over a vector,

don’t change it. 3. … !No runtime required.

Page 11: Guaranteeing Memory Safety in Rust

Credit where it is due

Rust borrows liberally from other great languages:

Rust has an active, amazing community.

C++, Haskell, ML/Ocaml, Cyclone, Erlang, Java, C#, …

Page 12: Guaranteeing Memory Safety in Rust

The Rust type system

Page 13: Guaranteeing Memory Safety in Rust

Observation

Aliasing Mutation

Danger arises from…

Hides dependencies. Causes memory to be freed.

auto& e = v[0]; v.push_back(…);…{ }

Page 14: Guaranteeing Memory Safety in Rust

Three basic patterns

fn foo(v: T) { … }

Ownership

fn foo(v: &T) { … }

Shared borrow

fn foo(v: &mut T) { … }

Mutable borrow

Page 15: Guaranteeing Memory Safety in Rust

Ownership!!n. The act, state, or right of possessing something.

Page 16: Guaranteeing Memory Safety in Rust

Ownership (T)

Aliasing Mutation

Page 17: Guaranteeing Memory Safety in Rust

vec

data

length

capacity

vec

data

length

capacity

1

2

fn give() { let mut vec = Vec::new(); vec.push(1); vec.push(2); take(vec); … }

fn take(vec: Vec<int>) { // … } !!!

Take ownership of a Vec<int>

Page 18: Guaranteeing Memory Safety in Rust

fn give() { let mut vec = Vec::new(); vec.push(1); vec.push(2); take(vec); … } vec.push(2);

Compiler enforces moves

fn take(vec: Vec<int>) { // … } !!!Error: vec has been moved

Prevents: - use after free - double moves - …

Page 19: Guaranteeing Memory Safety in Rust

Borrow!!v. To receive something with the promise of returning it.

Page 20: Guaranteeing Memory Safety in Rust

Shared borrow (&T)

Aliasing Mutation

Page 21: Guaranteeing Memory Safety in Rust

Mutable borrow (&mut T)

Aliasing Mutation

Page 22: Guaranteeing Memory Safety in Rust

fn lender() { let mut vec = Vec::new(); vec.push(1); vec.push(2); use(&vec); … }

fn use(vec: &Vec<int>) { // … } !!!

1

2vec

data

length

capacity

vec

“Shared reference to Vec<int>”

Loan out vec

Page 23: Guaranteeing Memory Safety in Rust

elem1

elem2

sum

fn dot_product(vec1: &Vec<int>, vec2: &Vec<int>) -> int { let mut sum = 0; for (elem1, elem2) in vec1.iter().zip(vec2.iter()) { sum += (*elem1) * (*elem2); } return sum; } walk down matching indices

elem1, elem2 are references into the vector

1

2

3

4

5

6*

vec1 vec2

Page 24: Guaranteeing Memory Safety in Rust

Why “shared” reference?

fn dot_product(vec1: &Vec<int>, vec2: &Vec<int>) -> int {…} !fn magnitude(vec: &Vec<int>) -> int { sqrt(dot_product(vec, vec)) }

two shared references to the same vector — OK!

Page 25: Guaranteeing Memory Safety in Rust

fn use(vec: &Vec<int>) { vec.push(3); vec[1] += 2; }

Shared references are immutable:

Error: cannot mutate shared reference

* Actually: mutation only in controlled circumstances

*

Aliasing Mutation

Page 26: Guaranteeing Memory Safety in Rust

fn push_all(from: &Vec<int>, to: &mut Vec<int>) { for elem in from.iter() { to.push(*elem); } }

Mutable references

mutable reference to Vec<int>

push() is legal

Page 27: Guaranteeing Memory Safety in Rust

Mutable references

1

2

3

from

to

elem

1

2

3

fn push_all(from: &Vec<int>, to: &mut Vec<int>) { for elem in from.iter() { to.push(*elem); } }

Page 28: Guaranteeing Memory Safety in Rust

What if from and to are equal?

1

2

3

from

to

elem

1

2

3

1

fn push_all(from: &Vec<int>, to: &mut Vec<int>) { for elem in from.iter() { to.push(*elem); } } dangling pointer

Page 29: Guaranteeing Memory Safety in Rust

fn push_all(from: &Vec<int>, to: &mut Vec<int>) {…} !fn caller() { let mut vec = …; push_all(&vec, &mut vec); }

shared referenceError: cannot have both shared and mutable reference at same time

A &mut T is the only way to access the memory it points at

Page 30: Guaranteeing Memory Safety in Rust

{ let mut vec = Vec::new(); … for i in range(0, vec.len()) { let elem: &int = &vec[i]; … vec.push(…); } … vec.push(…); }

Borrows restrict access to the original path for their duration.

Error: vec[i] is borrowed, cannot mutate

OK. loan expired.

&&mut

no writes, no movesno access at all

Page 31: Guaranteeing Memory Safety in Rust

Abstraction!!

n. freedom from representational qualities in art.

Page 32: Guaranteeing Memory Safety in Rust

Rust is an extensible language

• Zero-cost abstraction • Rich libraries: - Containers - Memory management - Parallelism - …

• Ownership and borrowing let libraries enforce safety.

Page 33: Guaranteeing Memory Safety in Rust

fn example() { let x: Rc<T> = Rc::new(…); { let y = x.clone(); let z = &*y; … } // runs destructor for y } // runs destructor for x

[0]x

y

1

Stack Heap

2 1

z

Page 34: Guaranteeing Memory Safety in Rust

Borrowing and Rcfn deref<‘a,T>(r: &’a Rc<T>) -> &’a T { … }

Given a borrowed reference to an `Rc<T>`…

…return a reference to the `T` inside with same extent

New reference can be thought of as a kind of sublease. Returned reference cannot outlast the original.

Page 35: Guaranteeing Memory Safety in Rust

fn example() -> &T { let x: Rc<T> = Rc::new(…); return &*x; } // runs destructor for x

Error: extent of borrow exceeds lifetime of `x`

Page 36: Guaranteeing Memory Safety in Rust

Concurrency!!n. several computations executing simultaneously, and potentially interacting with each other

Page 37: Guaranteeing Memory Safety in Rust

Data race

Two unsynchronized threads accessing same data!

where at least one writes.

✎ ✎✎ ✎

Page 38: Guaranteeing Memory Safety in Rust

Aliasing

Mutation

No ordering

Data race

Sound familiar?

Page 39: Guaranteeing Memory Safety in Rust

Messaging!(ownership)

Page 40: Guaranteeing Memory Safety in Rust

data

length

capacity

data

length

capacity

fn parent() { let (tx, rx) = channel(); spawn(proc() {…}); let m = rx.recv(); }

proc() { let m = Vec::new(); … tx.send(m); }

rx

tx

tx

m

Page 41: Guaranteeing Memory Safety in Rust

Shared read-only access!(ownership, borrowing)

Page 42: Guaranteeing Memory Safety in Rust

Arc<Vec<int>>

&Vec<int>

ref_count

data

length

capacity

[0]

[1]

Shared reference, so Vec<int> is immutable.

Vec<int>Owned, so no aliases.

(ARC = Atomic Reference Count)

Page 43: Guaranteeing Memory Safety in Rust

Locked mutable access!(ownership, borrowing)

✎✎

Page 44: Guaranteeing Memory Safety in Rust

fn sync_inc(mutex: &Mutex<int>) { let mut data = mutex.lock(); *data += 1; }

Destructor releases lockYields a mutable reference to data

Destructor runs here

Page 45: Guaranteeing Memory Safety in Rust

And beyond…

Parallelism is an area of active development. !

Either already have or have plans for: - Atomic primitives - Non-blocking queues - Concurrent hashtables - Lightweight thread pools - Futures - CILK-style fork-join concurrency - etc.

Always data-race free

Page 46: Guaranteeing Memory Safety in Rust

Parallel!!adj. occurring or existing at the same time

Page 47: Guaranteeing Memory Safety in Rust

Concurrent vs parallel

Blocks

Concurrent threads Parallel jobs

Page 48: Guaranteeing Memory Safety in Rust

fn qsort(vec: &mut [int]) { let pivot = vec[random(vec.len())]; let mid = vec.partition(vec, pivot); let (less, greater) = vec.mut_split_at(mid); qsort(less); qsort(greater); }

[0] [1] [2] [3] […] [n]

let vec: &mut [int] = …;

less greater

Page 49: Guaranteeing Memory Safety in Rust

fn parallel_qsort(vec: &mut [int]) { let pivot = vec[random(vec.len())]; let mid = vec.partition(vec, pivot); let (less, greater) = vec.mut_split(mid); parallel::do(&[ || parallel_qsort(less), || parallel_qsort(greater) ]); }

[0] [1] [2] [3] […] [n]

let vec: &mut [int] = …;

less greater

Page 50: Guaranteeing Memory Safety in Rust

Unsafe!!adj. not safe; hazardous

Page 51: Guaranteeing Memory Safety in Rust

Safe abstractions

unsafe { … }

• Uninitialized memory • Interfacing with C code • Building parallel abstractions like ARC • …

Trust me.

fn something_safe(…) { !!!!}

Validates input, etc.

Page 52: Guaranteeing Memory Safety in Rust

Status of Rust

“Rapidly stabilizing.”!!!Goal for 1.0: - Stable syntax, core type system - Minimal set of core libraries

Page 53: Guaranteeing Memory Safety in Rust

Conclusions

• Rust gives control without compromising safety: • Zero-cost abstractions • Zero-cost safety

• Guarantees beyond dangling pointers: • Iterator invalidation in a broader sense • Data race freedom