by Ferris the Crab Todd Smith
Rust Solution Architect
Xebia Functional
Where is Rust used?
Backend
Web
Mobile
Embedded
GUI
Games
Noteworthy features of Rust:
Imperative execution
Immutability by default
Algebraic data types
Higher-order functions
Type deduction (Hindley-Milner)
Traits (i.e., type classes)
Pattern matching
Top-tier performance
Type safety
Memory safety
Metaprogramming (macros, auto-derive)
No garbage collector!
Timeline of Rust:
2006: Created by Graydon Hoare at Mozilla
2009: Adopted officially at Mozilla
2015: First stable release
2022: Linux kernel, baby!
2023: Xebia Functional opens Rust service line
MEMORY SAFETY
(in C 😖)
extern /*@only@*/ int* glob;
/*@only@*/ int* f (
/*@only@*/ int* x,
int* y,
int* z
) /*@globals glob@*/
{
int* m = (int*) malloc(sizeof(int));
glob = y;
free(x);
*m = *x;
return z;
}
Splint
extern /*@only@*/ int* glob;
/*@only@*/ int* f (
/*@only@*/ int* x,
int* y,
int* z
) /*@globals glob@*/
{
int* m = (int*) malloc(sizeof(int));
glob = y;
free(x);
*m = *x;
return z;
}
> splint only.c
only.c:10: Only storage glob (type int *) not released
before assignment: glob = y
only.c:1: Storage glob becomes only
only.c:11: Implicitly temp storage y assigned to only:
glob = y
only.c:12: Dereference of possibly null pointer m: *m
only.c:9: Storage m may become null
only.c:12: Variable x used after being released
only.c:11: Storage x released
only.c:13: Implicitly temp storage z returned as only: z
only.c:13: Fresh storage m not released before return
only.c:9: Fresh storage m allocated
Splint
Hammurabi the Lawgiver
HammurabiFerris the Lawgiver
Ownership
The obligation to destroy a value when it goes out of scope.
struct Wrapped<T>(
T // Owner of some `T`
);
enum Either<A, B> {
A(A), // Owner of some `A`
B(B), // Owner of some `B`
}
fn main() {
let i = 5; // Owner of `5`
let w = Wrapped(i); // Owner of `Wrapped(5)`; `i` still lives
let a = Either::A(w); // Owner of `Either::A`; `w` destroyed by move
let _x = f(a); // Owner of some `Either`; `a` destroyed by move
} // `_x` destroyed by end of block
fn f<A>(
e: Either<Wrapped<A>, Wrapped<A>> // Owner of some `Either`
) -> Either<Wrapped<A>, Wrapped<A>> { // Returns ownership of some `Either`
match e {
Either::A(a) => // `a` is owner of `Wrapped`; `e` destroyed by move
Either::B(a), // Temporary is owner of `Either::B`
Either::B(b) => // `a` is owner of `Wrapped`; `e` destroyed by move
Either::A(b) // Temporary is owner of `Either::A`
} // Return ownership of some `Either`
}
Ownership
#[derive(Copy)] // Instances will be copied rather than moved
struct Point3D {
x: f64,
y: f64,
z: f64
}
Deriving the Copy trait
struct Wrapped<T>(
T // Owner of some `T`
);
enum Either<A, B> {
A(A), // Owner of some `A`
B(B), // Owner of some `B`
}
fn main() {
let i = 5; // Owner of `5`
let w = Wrapped(i); // Owner of `Wrapped(5)`; `i` still lives
let a = Either::A(w); // Owner of `Either::A`; `w` destroyed by move
let _x = f(a); // Owner of some `Either`; `a` destroyed by move
} // `_x` destroyed by end of block
fn f<A>(
e: Either<Wrapped<A>, Wrapped<A>> // Owner of some `Either`
) -> Either<Wrapped<A>, Wrapped<A>> { // Returns ownership of some `Either`
match e {
Either::A(a) => // `a` is owner of `Wrapped`; `e` destroyed by move
Either::B(a), // Temporary is owner of `Either::B`
Either::B(b) => // `a` is owner of `Wrapped`; `e` destroyed by move
Either::A(b) // Temporary is owner of `Either::A`
} // Return ownership of some `Either`
}
Ownership
int* x = (int*) malloc(sizeof(int)); // Allocate
free(x); // Single free - good
free(x); // Double free - bad
C loves double free — no error 🤦
let x = SomeStruct::default(); // Allocate
drop(x); // Single free - good
drop(x); // Double free - forbidden!
➜ cargo build
Compiling double-free v0.1.0 (/double-free)
error[E0382]: use of moved value: `x`
--> double-free/src/main.rs:4:10
|2 | let x = SomeStruct::default();
| - move occurs because `x` has type `SomeStruct`, which does not implement the `Copy` trait3 | drop(x);
| - value moved here4 | drop(x);
|^ value used here after move
For more information about this error, try `rustc --explain E0382`.
error: could not compile `double-free` (bin "double-free") due to previous error
Rust hates double free — compiler error 🙌
Borrow
Access to a value without the obligation to destroy that value when it
goes out of scope.
struct Wrapped<T>(
T // Owner of some `T`
);
enum Either<'a, 'b, A, B> {
A(&'a A), // Immutable borrow of some `A` with lifetime 'a
B(&'b B), // Immutable borrow of some `B` with lifetime 'b
}
fn main() {
let w = Wrapped(5); // Owner of `Wrapped(5)`
let mut a = Either::A(&w); // Mutable owner of `Either`; `w` immutably borrowed
f(&mut a); // `a` mutably borrowed, updated by call
} // `a` & `w` destroyed by end of block
fn f<'a, A>(
e: &mut Either<'a, 'a, A, A> // Mutable borrow of some `Either`
) {
*e = match e {
Either::A(a) => // `a` owns borrow of `A`
Either::B(a), // `e` becomes owner of new `Either`; `a` moved
Either::B(b) => // `a` owns borrow of `A`
Either::A(b) // `e` becomes owner of new `Either`; `b` moved
}
}
Borrowing
struct Wrapped<T>(
T // Owner of some `T`
);
enum Either<'a, 'b, A, B> {
A(&'a A), // Immutable borrow of some `A` with lifetime 'a
B(&'b B), // Immutable borrow of some `B` with lifetime 'b
}
fn main() {
let w = Wrapped(5); // Owner of `Wrapped(5)`
let mut a = Either::A(&w); // Mutable owner of `Either`; `w` immutably borrowed
f(&mut a); // `a` mutably borrowed, updated by call
} // `a` & `w` destroyed by end of block
fn f<'a, A>(
e: &mut Either<'a, 'a, A, A> // Mutable borrow of some `Either`
) {
*e = match e {
Either::A(a) => // `a` owns borrow of `A`
Either::B(a), // `e` becomes owner of new `Either`; `a` moved
Either::B(b) => // `a` owns borrow of `A`
Either::A(b) // `e` becomes owner of new `Either`; `b` moved
}
}
Borrowing
Rules of borrowing:
Either there may be one mutable borrow
Or there may be zero or many
immutable borrows
References must refer to live values, i.e., no dangling references
fn owner_dropped_while_borrowed() {
let outer_borrow: &i32;
{
let inner_borrow: &i32;
let owner: i32 = 10;
inner_borrow = &owner;
outer_borrow = inner_borrow;
}
println!("heh, heh = {}", outer_borrow);
}
Borrower, schmorrower
fn owner_dropped_while_borrowed() {
let outer_borrow: &i32;
{
let inner_borrow: &i32;
let owner: i32 = 10;
inner_borrow = &owner;
outer_borrow = inner_borrow;
}
println!("heh, heh = {}", outer_borrow);
}
➜ cargo buildCompiling rust-presentation-2023-11-09 v0.1.0 (/naive-borrow)
error[E0597]: `owner` does not live long enough
--> naive-borrow/src/main.rs:6:18
|5 | let owner: i32 = 10;
| ----- binding `owner` declared here6 | inner_borrow = &owner;
|^^^^^^ borrowed value does not live long enough7 | outer_borrow = inner_borrow;
8 | }
| - `owner` dropped here while still borrowed9 | println!("heh, heh = {}", outer_borrow);
| ------------ borrow later used here
For more information about this error, try `rustc --explain E0597`.
error: could not compile `naive-borrow` (bin "naive-borrow") due to previous error
fn owner_dropped_while_borrowed() {
let outer_owner = 10;
let mut outer_borrow = &outer_owner;
{
let inner_owner = 10;
let mut inner_borrow = &inner_owner;
annotated_assign_through_borrow(
&mut inner_borrow,
&mut outer_borrow
);
}
println!("heh, heh = {}", outer_borrow);
}
➜ cargo build
Compiling sneaky-borrow-with-lifetime v0.1.0 (/sneaky-borrow-with-lifetime)
error[E0597]: `inner_owner` does not live long enough
--> sneaky-borrow-with-lifetime/src/main.rs:13:26
|12 | let inner_owner = 10;
| ----------- binding `inner_owner` declared here13 | let mut inner_borrow = &inner_owner;
|^^^^^^^^^^^^ borrowed value does not live long enough...18 | }
| - `inner_owner` dropped here while still borrowed19 | println!("heh, heh = {}", outer_borrow);
| ------------ borrow later used here
For more information about this error, try `rustc --explain E0597`.
error: could not compile `sneaky-borrow-with-lifetime` (bin "sneaky-borrow-with-lifetime")
due to previous error
Curses, foiled again!
// Excerpted from standard library.
pub struct Box<
// Any type, even if size is statically unknown
T: ?Sized,
// To support "placement new"
A: Allocator = Global
>(Unique<T>, A); // `Unique` means "sole owner"
Box
Who owns E?
// Simplified from standard library.
pub struct RcInner<T: ?Sized> {
// Interiorly mutable strong count
strong: Cell<usize>,
// Interiorly mutable weak count (for cycle breaking)
weak: Cell<usize>,
// The shared value
value: T
}
pub struct Rc<T: ?Sized> {
// The shared value, plus reference counts
inner: Box<RcInner<T>>
}
Simplified Rc
pub struct GraphNode<T> {
// The payload
value: T,
// The vector of successors
successors: Vec<Rc<GraphNode<T>>>
}
Shared Ownership with Rc
// Simplified from standard library.
pub struct ArcInner<T: ?Sized> {
// Atomically mutable strong count
strong: atomic::AtomicUsize,
// Atomically mutable weak count (for cycle breaking)
weak: atomic::AtomicUsize,
// The shared value
value: T
}
pub struct Arc<T: ?Sized> {
// The shared value, plus reference counts
inner: Box<ArcInner<T>>
}
Simplified Arc
fn main() {
let shared: Arc<i32> = Arc::new(10);
let copy = Arc::clone(&shared);
let forked = spawn(move || {
println!("first thread: {}", *copy);
});
forked.join().unwrap();
println!("main thread: {}", *shared);
}
Share immutable data with Arc
fn main() {
let shared: Arc<Mutex<i32>> = Arc::new(Mutex::new(0));
let join_handles: Vec<JoinHandle<()>> = (1..=10)
.map(|_| { // Don't care about the subscript, only running 10 times
let copy = Arc::clone(&shared);
spawn(move || {
let mut guarded_value: MutexGuard<i32> = copy.lock().unwrap();
*guarded_value += 1;
})
})
.collect(); // Iterators are lazy, so must collect
// Wait for all threads to complete.
join_handles.into_iter().for_each(|h| h.join().unwrap());
println!("{}", *shared.lock().unwrap()); // Prints "10\n"
}
Share mutable data with Arc<Mutex>
fn main() {
let shared: Rc<i32> = Rc::new(10);
let copy = Rc::clone(&shared);
let forked = spawn(move ||
{
println!("first thread: {}", *copy);
}
);
forked.join().unwrap();
println!("main thread: {}", *shared);
}
Borrower, schmorrower
fn main() {
let shared: Rc<i32> = Rc::new(10);
let copy = Rc::clone(&shared);
let forked = spawn(move ||
{
println!("first thread: {}", *copy);
}
);
forked.join().unwrap();
println!("main thread: {}", *shared);
}
➜ cargo build
Compiling concurrent-rc v0.1.0 (/concurrent-rc)
error[E0277]: `Rc<i32>` cannot be sent between threads safely
-->; concurrent-rc/src/main.rs:7:21
|7 | let forked = spawn(move ||
| -----^------ | |||__________________|_____within this `[closure@concurrent-rc/src/main.rs:7:21: 7:28]`|||||required by a bound introduced by this call8 || {
9 || println!("first thread: {}", *copy);
10 || }
||_________^ `Rc<i32>` cannot be sent between threads safely|=help: within `[closure@concurrent-rc/src/main.rs:7:21: 7:28]`, the trait `Send` is not implemented for `Rc<i32>`
note: required because it's used within this closure
--> concurrent-rc/src/main.rs:7:21
|7 | let forked = spawn(move ||
|^^^^^^^note: required by a bound in `spawn`
--> /lib/rustlib/src/rust/library/std/src/thread/mod.rs:680:8
|680 | F: Send + 'static,
|^^^^ required by this bound in `spawn`
For more information about this error, try `rustc --explain E0277`.
error: could not compile `concurrent-rc` (bin "concurrent-rc") due to previous error
Curses, foiled again!
Traits in Rust:
Like interfaces in Java, Kotlin
Like traits in Scala
Like type classes in Haskell
May prescribe one or more methods
May provide default implementations of methods
May not specify state
Send
Auto trait that denotes types that can be moved across thread boundaries (because they are not subject to data races)
fn main() {
let shared: Rc<i32> = Rc::new(10);
let copy = Rc::clone(&shared);
let forked = spawn(move ||
{
println!("first thread: {}", *copy);
}
);
forked.join().unwrap();
println!("main thread: {}", *shared);
}
➜ cargo build
Compiling concurrent-rc v0.1.0 (/concurrent-rc)
error[E0277]: `Rc<i32>` cannot be sent between threads safely
-->; concurrent-rc/src/main.rs:7:21
|7 | let forked = spawn(move ||
| -----^------ | |||__________________|_____within this `[closure@concurrent-rc/src/main.rs:7:21: 7:28]`|||||required by a bound introduced by this call8 || {
9 || println!("first thread: {}", *copy);
10 || }
||_________^ `Rc<i32>` cannot be sent between threads safely|=help: within `[closure@concurrent-rc/src/main.rs:7:21: 7:28]`, the trait `Send` is not implemented for `Rc<i32>`
note: required because it's used within this closure
--> concurrent-rc/src/main.rs:7:21
|7 | let forked = spawn(move ||
|^^^^^^^note: required by a bound in `spawn`
--> /lib/rustlib/src/rust/library/std/src/thread/mod.rs:680:8
|680 | F: Send + 'static,
|^^^^ required by this bound in `spawn`
For more information about this error, try `rustc --explain E0277`.
error: could not compile `concurrent-rc` (bin "concurrent-rc") due to previous error
🤔
fn main() {
let shared: Arc<Rc<i32>> =
Arc::new(Rc::new(10));
let copy = Arc::clone(&shared);
let forked = spawn(move ||
{
println!("first thread: {}", **copy);
}
);
forked.join().unwrap();
println!("main thread: {}", **shared);
}
Borrower, schmorrower
fn main() {
let shared: Arc<Rc<i32>> =
Arc::new(Rc::new(10));
let copy = Arc::clone(&shared);
let forked = spawn(move ||
{
println!("first thread: {}", **copy);
}
);
forked.join().unwrap();
println!("main thread: {}", **shared);
}
➜ cargo build
Compiling concurrent-rc-in-arc v0.1.0 (/concurrent-rc-in-arc)
error[E0277]: `Rc<i32>` cannot be shared between threads safely
--> concurrent-rc-in-arc/src/main.rs:8:21
|8 | let forked = spawn(move ||
|__________________-----_^|||||required by a bound introduced by this call9 || {
10 || println!("first thread: {}", **copy);
11 || }
||_________^ `Rc<i32>` cannot be shared between threads safely|=help: the trait `Sync` is not implemented for `Rc<i32>`
=note: required for `Arc<Rc<i32>>` to implement `Send`
note: required because it's used within this closure
--> concurrent-rc-in-arc/src/main.rs:8:21
|8 | let forked = spawn(move ||
|^^^^^^^note: required by a bound in `spawn`
--> /lib/rustlib/src/rust/library/std/src/thread/mod.rs:680:8
|680 | F: Send + 'static,
|^^^^ required by this bound in `spawn`…
For more information about this error, try `rustc --explain E0277`.
error: could not compile `concurrent-rc-in-arc` (bin "concurrent-rc-in-arc") due to previous error
Curses, foiled again!
Sync
Auto trait that denotes types that can be shared across thread boundaries (because they are not subject to data races)
Rules for Send and Sync
(which I am definitely not talking about in this presentation)
A type is Sendiff it is safe to move to another thread.
A type is Synciff it is safe to share with other threads.
T is Synciff&T
is Send.
A compound type that is composed entirely of Send or
Sync types, then it is Send or
Sync.
Raw pointers (*const T and *mut T are neither
Send nor Sync.