An introduction to rust
Performance. Reliability. Productivity.
Cyril Soldani
University of Liège
22nd of February 2019
0
Outline
1 Why?
2 What?
3 Discussion
1
Outline
1 Why?
2 What?
3 Discussion
2
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
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
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
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
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
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
Outline
1 Why?
2 What?
The borrow checker
Some other language features
3 Discussion
9
Hello, World!
1 fn main() {
2 // A very original first program
3 println!("Hello, World!");
4 }
10
A trivial (?) bug
1 vector<int> v;
2 v.push_back(1);
3 int& x = &v[0];
4 v.push_back(2);
5 cout << x << endl;
11
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;
11
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
11
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
In rust, everything is immutable by default
1 let x = 42;
2 x = 1984;
3 println!("x = {}", x);
12
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
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
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
Move semantics
1 fn eat_vec(v: Vec<i32>) {
2 // ...
3 }
4
5 fn main() {
6 let mut v = Vec::new();
7 v.push(1);
8 eat_vec(v);
9 v.push(2);
10 }
14
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
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
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
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
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
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
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
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
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
Generic functions
1 fn guess_what<T>(x: T) -> T {
2 // ?
3 }
21
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
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
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
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
Enumerations
1 enum Button {
2 Left, Middle, Right,
3 }
4
5 enum Event {
6 Move { x: i32, y: i32 },
7 Click(Button),
8 KeyPress(char),
9 }
10
11 fn main() {
12 let e = Event::Click(Button::Left);
13 }
23
Pattern matching: efficient and legible if-else-if
1 fn process(e: Event) {
2 use Event::*;
3 match e {
4 KeyPress(c) => println!("Pressed key {}", c),
5 Move {x, y} => {
6 println!("Moved to ({},{})", x, y);
7 }
8 }
9 }
24
Pattern matching: efficient and legible if-else-if
1 fn process(e: Event) {
2 use Event::*;
3 match e {
4 KeyPress(c) => println!("Pressed key {}", c),
5 Move {x, y} => {
6 println!("Moved to ({},{})", x, y);
7 }
8 }
9 }
error[E0004]: non-exhaustive patterns: ‘Click(_)‘ not covered
24
Pattern matching: efficient and legible if-else-if
1 fn process(e: Event) {
2 use Event::*;
3 match e {
4 KeyPress(c) => println!("Pressed key {}", c),
5 Move {x, y} => {
6 println!("Moved to ({},{})", x, y);
7 }
8 }
9 }
error[E0004]: non-exhaustive patterns: ‘Click(_)‘ not covered
Filter matches with guards: X(i) if i < 3 =>.
Combine multiple arms with One | Two =>.
Ranges: 12 .. 19 =>.
Bindings: name @ ... =>.
Partial matches with _ and ...
24
Generic enumerations
1 enum Option<T> {
2 /// No value
3 None,
4 /// Some value `T`
5 Some(T),
6 }
25
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
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
Error Handling
1 let mut s = String::new();
2
3 match io::stdin().read_line(&mut s) {
4 Ok(_) => (),
5 Err(msg) =>
6 eprintln!("Cannot read line: {}", msg),
7 }
8
9 io::stdin().read_line(&mut s)
10 .expect("Panic: cannot read line!");
26
Simplify error handling with try!
1 fn write_to_file_using_try() -> Result<(), io::Error> {
2 let mut file = try!(File::create("friends.txt"));
3 try!(file.write_all(b"JohnnMaryn"));
4 println!("I wrote to the file");
5 Ok(())
6 }
7 // This is equivalent to:
8 fn write_to_file_using_match() -> Result<(), io::Error> {
9 let mut file = match File::create("friends.txt") {
10 Ok(f) => f,
11 Err(e) => return Err(e),
12 };
13 match file.write_all(b"JohnnMaryn") {
14 Ok(x) => x,
15 Err(e) => return Err(e)
16 };
17 println!("I wrote to the file");
18 Ok(())
19 }
27
Methods
1 struct Circle { radius: f64, center: Point<f64> }
2
3 impl Circle {
4 fn area(&self) -> f64 {
5 PI * (self.radius * self.radius)
6 }
7
8 fn perimeter(&self) -> f64 {
9 2.0 * PI * self.radius
10 }
11 }
12
13 fn main() {
14 let center = Point { x: 0.0, y: 0.0 };
15 let circle = Circle { radius: 2.0, center };
16 println!("circle.area() = {}", circle.area())
17 }
28
Traits
1 pub trait Hash {
2 fn hash<H: Hasher>(&self, state: &mut H);
3 }
4 impl Hash for Circle {
5 fn hash<H: Hasher>(&self, state: &mut H) {
6 self.center.hash(state);
7 self.radius.hash(state);
8 }
9 }
29
Traits
1 pub trait Hash {
2 fn hash<H: Hasher>(&self, state: &mut H);
3 }
4 impl Hash for Circle {
5 fn hash<H: Hasher>(&self, state: &mut H) {
6 self.center.hash(state);
7 self.radius.hash(state);
8 }
9 }
Some traits support automatic implementation!
1 #[derive(PartialEq, Eq, Hash)]
2 struct Circle { /* ... */ }
29
Traits bounds
1 fn store<T: Hash>(x: T, warehouse: Warehouse<T>) {
2 // ... x.hash(state) ...
3 }
30
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 }
30
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
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
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
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
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
Outline
1 Why?
2 What?
3 Discussion
34
Questions and (possibly) answers
?And happy rusting!
35

Le langage rust

  • 1.
    An introduction torust Performance. Reliability. Productivity. Cyril Soldani University of Liège 22nd of February 2019 0
  • 2.
  • 3.
  • 4.
    Rust fits aninteresting 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 designedfor 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 Ifno 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 goodfor 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 In2018 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 2006start 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
  • 10.
    Outline 1 Why? 2 What? Theborrow checker Some other language features 3 Discussion 9
  • 11.
    Hello, World! 1 fnmain() { 2 // A very original first program 3 println!("Hello, World!"); 4 } 10
  • 12.
    A trivial (?)bug 1 vector<int> v; 2 v.push_back(1); 3 int& x = &v[0]; 4 v.push_back(2); 5 cout << x << endl; 11
  • 13.
    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; 11
  • 14.
    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 11
  • 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, everythingis immutable by default 1 let x = 42; 2 x = 1984; 3 println!("x = {}", x); 12
  • 17.
    In rust, everythingis 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, everythingis 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 ownsthe 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
  • 20.
    Move semantics 1 fneat_vec(v: Vec<i32>) { 2 // ... 3 } 4 5 fn main() { 6 let mut v = Vec::new(); 7 v.push(1); 8 eat_vec(v); 9 v.push(2); 10 } 14
  • 21.
    Move semantics 1 fneat_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 fneat_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 fnmutate_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 referenceis 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 referenceis 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 tracksthe 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 caninfer 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 checkerallows 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
  • 30.
    Generic functions 1 fnguess_what<T>(x: T) -> T { 2 // ? 3 } 21
  • 31.
    Generic functions 1 fnguess_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 fnidentity<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 fnidentity<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 structPoint<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
  • 35.
    Enumerations 1 enum Button{ 2 Left, Middle, Right, 3 } 4 5 enum Event { 6 Move { x: i32, y: i32 }, 7 Click(Button), 8 KeyPress(char), 9 } 10 11 fn main() { 12 let e = Event::Click(Button::Left); 13 } 23
  • 36.
    Pattern matching: efficientand legible if-else-if 1 fn process(e: Event) { 2 use Event::*; 3 match e { 4 KeyPress(c) => println!("Pressed key {}", c), 5 Move {x, y} => { 6 println!("Moved to ({},{})", x, y); 7 } 8 } 9 } 24
  • 37.
    Pattern matching: efficientand legible if-else-if 1 fn process(e: Event) { 2 use Event::*; 3 match e { 4 KeyPress(c) => println!("Pressed key {}", c), 5 Move {x, y} => { 6 println!("Moved to ({},{})", x, y); 7 } 8 } 9 } error[E0004]: non-exhaustive patterns: ‘Click(_)‘ not covered 24
  • 38.
    Pattern matching: efficientand legible if-else-if 1 fn process(e: Event) { 2 use Event::*; 3 match e { 4 KeyPress(c) => println!("Pressed key {}", c), 5 Move {x, y} => { 6 println!("Moved to ({},{})", x, y); 7 } 8 } 9 } error[E0004]: non-exhaustive patterns: ‘Click(_)‘ not covered Filter matches with guards: X(i) if i < 3 =>. Combine multiple arms with One | Two =>. Ranges: 12 .. 19 =>. Bindings: name @ ... =>. Partial matches with _ and ... 24
  • 39.
    Generic enumerations 1 enumOption<T> { 2 /// No value 3 None, 4 /// Some value `T` 5 Some(T), 6 } 25
  • 40.
    Generic enumerations 1 enumOption<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 enumOption<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
  • 42.
    Error Handling 1 letmut s = String::new(); 2 3 match io::stdin().read_line(&mut s) { 4 Ok(_) => (), 5 Err(msg) => 6 eprintln!("Cannot read line: {}", msg), 7 } 8 9 io::stdin().read_line(&mut s) 10 .expect("Panic: cannot read line!"); 26
  • 43.
    Simplify error handlingwith try! 1 fn write_to_file_using_try() -> Result<(), io::Error> { 2 let mut file = try!(File::create("friends.txt")); 3 try!(file.write_all(b"JohnnMaryn")); 4 println!("I wrote to the file"); 5 Ok(()) 6 } 7 // This is equivalent to: 8 fn write_to_file_using_match() -> Result<(), io::Error> { 9 let mut file = match File::create("friends.txt") { 10 Ok(f) => f, 11 Err(e) => return Err(e), 12 }; 13 match file.write_all(b"JohnnMaryn") { 14 Ok(x) => x, 15 Err(e) => return Err(e) 16 }; 17 println!("I wrote to the file"); 18 Ok(()) 19 } 27
  • 44.
    Methods 1 struct Circle{ radius: f64, center: Point<f64> } 2 3 impl Circle { 4 fn area(&self) -> f64 { 5 PI * (self.radius * self.radius) 6 } 7 8 fn perimeter(&self) -> f64 { 9 2.0 * PI * self.radius 10 } 11 } 12 13 fn main() { 14 let center = Point { x: 0.0, y: 0.0 }; 15 let circle = Circle { radius: 2.0, center }; 16 println!("circle.area() = {}", circle.area()) 17 } 28
  • 45.
    Traits 1 pub traitHash { 2 fn hash<H: Hasher>(&self, state: &mut H); 3 } 4 impl Hash for Circle { 5 fn hash<H: Hasher>(&self, state: &mut H) { 6 self.center.hash(state); 7 self.radius.hash(state); 8 } 9 } 29
  • 46.
    Traits 1 pub traitHash { 2 fn hash<H: Hasher>(&self, state: &mut H); 3 } 4 impl Hash for Circle { 5 fn hash<H: Hasher>(&self, state: &mut H) { 6 self.center.hash(state); 7 self.radius.hash(state); 8 } 9 } Some traits support automatic implementation! 1 #[derive(PartialEq, Eq, Hash)] 2 struct Circle { /* ... */ } 29
  • 47.
    Traits bounds 1 fnstore<T: Hash>(x: T, warehouse: Warehouse<T>) { 2 // ... x.hash(state) ... 3 } 30
  • 48.
    Traits bounds 1 fnstore<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 } 30
  • 49.
    Traits bounds 1 fnstore<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 andinheritance 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 andinheritance 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 theheap 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 andsafe 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
  • 54.
  • 55.
    Questions and (possibly)answers ?And happy rusting! 35