4. Rust fits an interesting niche
System programming is needed for:
low-level stuff like Operating Systems;
applications that need to run fast;
cheap/efficient embedded systems.
Your python toolkit runs more C/C++/Fortran than python!
Although everything else relies on it, system programming is
notoriously error-prone!
Rust makes system programming easier and much safer.
But rust is not just for system programming!
3
5. Rust is designed for performance
Very much like C++, rust:
is statically typed;
compiles to native executables;
has no runtime;
has no garbage collector;
give you control of memory layout;
integrates easily with C code;
has zero-cost abstractions.
=⇒ Idiomatic rust performance is on par with idiomatic C/C++.
4
6. Rust is reliable
If no possible execution can exhibit undefined behaviour, we say
that a program is well-defined.
If a language’s type system enforces that every program is
well-defined, we say it is type-safe.
C/C++ are not type-safe:
char buf[16];
buf[24] = 42; // Undefined behaviour
Rust is type-safe:
Guaranteed memory-safety:
No dereference of null pointers.
No dangling pointers.
No buffer overflow.
Guaranteed thread-safety:
Threads without data races.
5
7. Rust is good for productivity
High-level features: traits, pattern matching, closures, etc.
Great documentation.
Good error messages.
Built-in testing tools.
Built-in package manager and build tool (cargo).
Built-in documentation tool.
Good IDE integration (auto-completion, type inspection,
formatting, etc.).
Great community.
6
8. Rust is popular
StackOverflow
In 2018 survey: 78.9% of rust users love the language
. . . which makes it the most loved language
. . . for the 3rd year in a row!
GitHub
rust-lang/rust:
1.3k 34k 5.5k 90k 2.3k
4.4k active repositories with rust code.
Still on the rise, gathering momentum!
7
9. Rust is mature
History
2006 start of development by G. Hoare (Mozilla Research).
2009 endorsed by Mozilla.
2010 rust is announced.
2011 rustc is self-hosting.
2012 first public release.
2015 first stable release: Rust 1.0.
...
2019 Rust 1.32, development still active!
The language changed a lot in its infancy, but is now rather stable.
Used for various projects by Mozilla, Samsung, Dropbox, NPM,
Oracle, Microsoft, . . .
8
15. A trivial (?) bug
1 vector<int> v;
2 v.push_back(1);
3 int& x = &v[0];
4 v.push_back(2); // Re-allocate buffer
5 cout << x << endl; // Dangling pointer!!!
segmentation fault
Problems happen when a resource is both
aliased, i.e. there are multiple references to it;
mutable, i.e. one can modify the resource.
=⇒ restrict mutability or aliasing!
11
16. In rust, everything is immutable by default
1 let x = 42;
2 x = 1984;
3 println!("x = {}", x);
12
17. In rust, everything is immutable by default
1 let x = 42;
2 x = 1984; // Error!
3 println!("x = {}", x);
error[E0384]: cannot assign twice to immutable variable ‘x‘
12
18. In rust, everything is immutable by default
1 let mut x = 42;
2 x = 1984;
3 println!("x = {}", x);
x = 1984
Use mut when something should be mutable.
12
19. Ownership
A binding owns the resources it is bound to.
Bound resource is deallocated automatically and
deterministically when its binding goes out of scope.
In a given scope, bindings are freed in LIFO order.
1 {
2 let mut v1 = Vec::new();
3 let mut v2 = Vec::new();
4 // ...
5 // Release v2, then v1
6 }
13
21. Move semantics
1 fn eat_vec(v: Vec<i32>) {
2 // ..., then release v
3 }
4
5 fn main() {
6 let mut v = Vec::new();
7 v.push(1);
8 eat_vec(v);
9 v.push(2); // Error!
10 }
error[E0382]: use of moved value: ‘v‘
14
22. Move semantics
1 fn eat_vec(v: Vec<i32>) {
2 // ..., then release v
3 }
4
5 fn main() {
6 let mut v = Vec::new();
7 v.push(1);
8 eat_vec(v);
9 v.push(2); // Error!
10 }
error[E0382]: use of moved value: ‘v‘
Solutions:
Return value.
Copy value.
Lend value.
14
23. Borrowing
1 fn borrow_vec(v: &Vec<i32>) {
2 // ...
3 }
4
5 fn main() {
6 let mut v = Vec::new();
7 v.push(1);
8 borrow_vec(&v);
9 v.push(2);
10 }
&T is a reference.
A reference only borrows ownership, will not deallocate.
Multiple references to same resource is OK.
Explicit for both caller and callee.
15
24. Mutable borrow
1 fn mutate_vec(v: &mut Vec<i32>) {
2 v[0] = 42;
3 }
4
5 fn main() {
6 let mut v = Vec::new();
7 v.push(1);
8 mutate_vec(&mut v);
9 v.push(2);
10 }
&mut T is a mutable reference.
There can be only one mutable reference to a resource.
Explicit for both caller and callee.
16
25. If a reference is mutable, it is the only reference
1 fn main() {
2 let mut x = 1984;
3 let x_ref_1 = &x;
4 let x_ref_2 = &x; // Multiple refs is OK
5
6 let mut y = 42;
7 let y_mut_ref = &mut y; // OK, single ref
8
9 let mut z = 2001;
10 let z_mut_ref = &mut z; // Error!
11 let z_ref = &z;
12 }
error[E0502]: cannot borrow ‘z‘ as mutable because it is also
borrowed as immutable
17
26. If a reference is mutable, it is the only reference
1 fn main() {
2 let mut x = 1984;
3 let x_ref_1 = &x;
4 let x_ref_2 = &x; // Multiple refs is OK
5
6 let mut y = 42;
7 let y_mut_ref = &mut y; // OK, single ref
8
9 let mut z = 2001;
10 let z_mut_ref = &mut z; // Error!
11 let z_ref = &z;
12 }
Note that access through x, y, z is restricted as well.
17
27. Rust also tracks the lifetime of resources
2 let x;
3 { // Enter new scope
4 let y = 42;
5 x = &y; // x is a ref to y
6 } // Leave scope, destroy y
7 println!("x = {}", x); // What does x points to?
error[E0597]: `y` does not live long enough
--> test.rs:5:14
|
5 | x = &y; // x is a ref to y
| ˆ borrowed value does not live long enough
6 | } // Leave scope, destroy y
| - `y` dropped here while still borrowed
7 | println!("x = {}", x);
8 | }
| - borrowed value needs to live until here
18
28. The compiler can infer lifetimes, but sometimes needs a
little help
1 fn choose(s1: &str, s2: &str) -> &str { s1 }
2
3 fn main() {
4 let s = choose("foo", "bar");
5 println!("s = {}", s);
6 }
error[E0106]: missing lifetime specifier
help: this function’s return type contains a borrowed value, but
the signature does not say whether it is borrowed from ‘s1‘ or ‘s2‘
1 fn choose<'a>(s1: &'a str, s2: &str) -> &'a str
'static is for immortals.
19
29. The borrow checker allows for fearless concurrency
1 fn use_lock(mutex: &Mutex<Vec<i32>>) {
2 let vec = {
3 // Acquire the lock
4 let mut guard = lock(mutex);
5 // Attempt to return a borrow of the data
6 access(&mut guard)
7 // Guard is destroyed here, releasing the lock
8 };
9 // Attempt to access the data outside of the lock.
10 vec.push(3); // Error: guard does not live long enough
11 }
Concurrency not only safe but easy:
Locks know their data, which can’t be accidentally shared.
Channels move data between threads.
Many readers, one writer extends to thread-shared data.
You can share even stack frames, without disaster!
20
31. Generic functions
1 fn guess_what<T>(x: T) -> T {
2 // ?
3 }
Parametric types are constrained.
1 fn min<T: Ord>(x: T, y: T) {
2 if x < y { x } else { y }
3 }
21
32. Generic functions
1 fn identity<T>(x: T) -> T {
2 x
3 }
Parametric types are constrained.
1 fn min<T: Ord>(x: T, y: T) {
2 if x < y { x } else { y }
3 }
21
33. Generic functions
1 fn identity<T>(x: T) -> T {
2 x
3 }
Parametric types are constrained.
1 fn min<T: Ord>(x: T, y: T) {
2 if x < y { x } else { y }
3 }
=⇒ Theorems for free!
21
34. Generic types
1 struct Point<T> {
2 x: T,
3 y: T
4 }
5
6 fn main() {
7 let p_f32: Point<f32> = Point { x: 1.2, y: 3.4 };
8 let mut p_i64 = Point { x: 1, y: 2 };
9 let p_i32 = Point { x: 1, y: 2 };
10 let p_f64 = Point { x: 3.14, y: 1.592 };
11
12 p_i64.x = 42i64;
13 }
22
40. Generic enumerations
1 enum Option<T> {
2 /// No value
3 None,
4 /// Some value `T`
5 Some(T),
6 }
Option used for optional values, partial functions, etc. No NULL!
25
41. Generic enumerations
1 enum Option<T> {
2 /// No value
3 None,
4 /// Some value `T`
5 Some(T),
6 }
Option used for optional values, partial functions, etc. No NULL!
1 enum Result<T, E> {
2 //! Contains the success value
3 Ok(T),
4 //! Contains the error value
5 Err(E),
6 }
Result used for error handling. No exceptions!
25
49. Traits bounds
1 fn store<T: Hash>(x: T, warehouse: Warehouse<T>) {
2 // ... x.hash(state) ...
3 }
One can specify multiple bounds:
1 fn store<T>(x: T, warehouse: Warehouse<T>)
2 where T: Hash + Display {
3 // ... x.hash(state) ...
4 // ... println!("x = {}", x) ...
5 }
Monomorphisation applies, i.e. statically dispatched.
Dynamic dispatch is also available with trait objects.
30
50. Default methods and inheritance
1 trait Valid {
2 fn is_valid(&self) -> bool;
3 fn is_invalid(&self) -> bool { !self.is_valid() };
4 }
Default implementation can be overridden.
31
51. Default methods and inheritance
1 trait Valid {
2 fn is_valid(&self) -> bool;
3 fn is_invalid(&self) -> bool { !self.is_valid() };
4 }
Default implementation can be overridden.
Traits can inherit from other traits:
1 trait Eq : PartialEq {
2 // Addtional methods in Eq
3 }
31
52. Allocating on the heap
By default, everything is stack-allocated:
Fast.
Scoped.
Limited in size.
Various explicit pointer types for heap-allocated data:
Box<T> for single ownership.
Rc<T> for shared ownership.
Arc<T> for shared, thread-safe ownership.
Cell<T> for interior mutability, i.e. through a &T.
32
53. Undiscussed bits
Efficient and safe slices.
Hygienic macros.
unsafe code.
Interactions with C-ABI code.
Iterators: clean syntax, safe, efficient, e.g.
for x in xs {
process(x)
}
Closures.
Multi-threading.
Embedded rust.
The ecosystem: cargo and crates.io, rustup, tests, doc, . . .
Many, many more!
33