Rust: An Introduction

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

By Mbzt - Own work, CC BY 3.0, https://commons.wikimedia.org/w/index.php?curid=59794940

Hammurabi the Lawgiver

Ferris Speaks

Hammurabi Ferris 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

Spy Shhh
							
#[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` trait
3 |     drop(x);
  |          - value moved here
4 |     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

Snidely Whiplash, trying to break Rust

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 build
   Compiling 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 here
6 |         inner_borrow = &owner;
  |                        ^^^^^^ borrowed value does not live long enough
7 |         outer_borrow = inner_borrow;
8 |     }
  |     - `owner` dropped here while still borrowed
9 |     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
					

Curses, foiled again!

									
fn assign_through_borrow(
	a: &mut &i32,
	b: &mut &i32
) {
	*b = *a;
}

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;
		assign_through_borrow(
			&mut inner_borrow,
			&mut outer_borrow
		);
	}
	println!("heh, heh = {}", outer_borrow);
}
									
								

Borrower, schmorrower

Snidely Whiplash, trying to break Rust
						
fn assign_through_borrow(
	a: &mut &i32,
	b: &mut &i32
) {
	*b = *a;
}
						
					
➜ cargo build
   Compiling sneaky-borrow v0.1.0 (/sneaky-borrow)
error: lifetime may not live long enough
 --> sneaky-borrow/src/main.rs:5:2
  |
2 |     a: &mut &i32,
  |             - let's call the lifetime of this reference `'1`
3 |     b: &mut &i32
  |             - let's call the lifetime of this reference `'2`
4 | ) {
5 |     *b = *a;
  |     ^^^^^^^ assignment requires that `'1` must outlive `'2`
  |
help: consider introducing a named lifetime parameter
  |
1 ~ fn assign_through_borrow<'a>(
2 ~     a: &mut &'a i32,
3 ~     b: &mut &'a i32
  |

error: could not compile `sneaky-borrow` (bin "sneaky-borrow") due to previous error
					

Curses, foiled again!

									
fn annotated_assign_through_borrow<
	'a: 'b,
	'b
>(
	a: &mut &'a i32,
	b: &mut &'b i32,
) {
	*b = *a;
}

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);
}
									
								

Borrower, schmorrower

Snidely Whiplash, trying to break Rust
						
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 here
13 |         let mut inner_borrow = &inner_owner;
   |                                ^^^^^^^^^^^^ borrowed value does not live long enough
...
18 |     }
   |     - `inner_owner` dropped here while still borrowed
19 |     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"
					
Graph

Box

Graph

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>>>
}
					
Graph with Rc

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

Snidely Whiplash, trying to break Rust
						
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 call
8   | |         {
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 call
8   | |         {
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

Snidely Whiplash, trying to break Rust
						
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 call
9   | |         {
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)
Spy Shhh

Rules for Send and Sync

(which I am definitely not talking about in this presentation)

  • A type is Send iff it is safe to move to another thread.
  • A type is Sync iff it is safe to share with other threads.
  • T is Sync iff &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.
  • Cell, RefCell, and UnsafeCell are not Sync.
  • Rc is not Send or Sync! 😀

MEMORY SAFETY
==
THREAD SAFETY

(in Rust 🦀 !)

Official Rust resources:

Community Rust resources:

Internal Xebia Functional Rust resources:

Please request access if you don't have it!