Safety in Concurrent
Programming with Rust
CS3211 Parallel and Concurrent Programming
Outline
• Challenges in concurrent programming
• Introducing Rust
• Safety, ownership and borrowing
• Concurrency in Rust
• Threads, Mutex, Atomic
• Libraries: Crossbeam, Rayon
CS3211 L10 - Safety in Rust 2
Challenges in concurrent programming
• Parallelizing … anything is a daunting task
• The goal is to make things faster
• Many times, parallelizing is done by just adding another instance that does
the same work
• Race conditions, data races, deadlocks, starvation
• Unsafe usage of memory in C/C++
• Use after free (UAF)
• Double free
CS3211 L10 - Safety in Rust 3
Fearless Concurrency in Rust
• Rust was initiated with two goals in mind:
• Safety in systems programming
• Painless concurrency
CS3211 L10 - Safety in Rust 4
Rust nowadays
• Strong safety guarantees
• No seg-faults, no data races, expressive type system
• Without compromising on performance
• No garbage collector, no runtime
• Same level of performance as C/C++
• Goal
• Confident, productive systems programming
CS3211 L10 - Safety in Rust 5
C++ is unsafe
• Vector is freed when we exit the scope
CS3211 L10 - Safety in Rust 6
C++ is unsafe
• Dangling pointers issues
CS3211 L10 - Safety in Rust 7
C++ is unsafe
• Aliased pointers – pointers that point
to the same chunk of memory
• elem and vector[0]
• Mutation – changing a pointer
• Aliasing + mutation – changing
(modifying) pointers that point to the
same chunk of memory
CS3211 L10 - Safety in Rust 8
Solution
• Ownership and borrowing
• Prevent simultaneous mutation and aliasing
• No runtime like in C++
• Memory safety like in garbage-collected languages
• No data races like in …Rust
CS3211 L10 - Safety in Rust 9
Ownership (1)
• Lines 2-4:
• Vector book is initialized
• Owner: main function
CS3211 L10 - Safety in Rust 10
Ownership (2)
• Line 5: give ownership to publish
• Pass the without &
• Runtime
• Copy over the fields from main’s stack
to publish’s stack
• Forget about the first book in main
• Line 12: runs the destructor for
book
CS3211 L10 - Safety in Rust 11
Ownership (3)
• Line 5: give ownership to publish
• Line 8: compilation error
• Error: use of moved value book
Ownership does not allow aliasing!
CS3211 L10 - Safety in Rust 12
Rust ownership compared to C++
• Rust: giving ownership is the default
• Not like the copy constructor in C++
• A bit like a move in C++, but enforced at compilation time and no ownership is
retained
• Rust: deep copy of data is explicit using clone()
• In C++, the copy constructor does a deep copy
CS3211 L10 - Safety in Rust 13
Immutable borrow
(Shared borrow)
• Line 12: type is a reference to a
vector –> use &
• Line 5, 10: borrow the vector,
creating an immutable (shared)
reference
A shared borrow allows aliasing, but no mutation!
CS3211 L10 - Safety in Rust 14
Consequences of immutable borrow
• Line 4: vector is (shared) borrowed here
• Freezes the whole container vector
• Line 9: cannot mutate (compilation error)
A shared borrow allows aliasing, but no mutation!
CS3211 L10 - Safety in Rust 15
Mutable borrow
• Line 9: mutable reference to a vector
• Line 5, 6: 2 successful mutable borrows
CS3211 L10 - Safety in Rust 16
Shared borrow: “Don’t break your friend’s toys”
A shared borrow allows aliasing, but no mutation!
CS3211 L10 - Safety in Rust 17
Mutable borrow: “No, it’s my turn now!”
A mutable borrow allows mutation, but no aliasing!
CS3211 L10 - Safety in Rust 18
Memory safety in Rust
• The borrow checker statically prevents aliasing + mutation
• Compile time
• Fighting the borrow checker!
• Don’t give up! Don’t use unsafe!
• Ownership prevents double-free
• The owner frees
• Borrowing prevents use-after-free
• No segfaults! Type Ownership Alias? Mutate?
T Owned Yes
&T Shared reference Yes
&mut T Mutable reference Yes
CS3211 L10 - Safety in Rust 19
No data races in Rust
• Data race = sharing + mutation + no ordering
• Sharing + mutation are prevented in Rust
CS3211 L10 - Safety in Rust 20
Library-based concurrency
• Not build into the language
• Rust had message passing build into the language – removed
• Library-based – in std or other libraries
• Multi-paradigm
• Leverage on ownership/borrowing
CS3211 L10 - Safety in Rust 21
Rust tools
• Rustup – to install your rust tools
• Rustc – the Rust compiler
• Cargo
• Calls the compiler – rustc
• TOML (Tom’s Obvious, Minimal Language) format for the configuration file
• Packages, crates, modules
• A package is one or more crates that provide a set of functionality
• A crate is a binary or library
• Modules are used to organize code within a crate into groups
• Privacy control
CS3211 L10 - Safety in Rust 22
Create a thread
• Line 1: create a thread
• loc is a JoinHandle
• If loc is dropped, the spawned thread is detached
• Line 5: join a thread
CS3211 L10 - Safety in Rust 23
Transfer the vector to a thread
• move converts any variables
captured by reference or mutable
reference to variables captured by
value
• move keyword: the closure will take
ownership of the values it uses from
the environment, thus transferring
ownership of those values from one
thread to another
• Line 5: error:
use after move
CS3211 L10 - Safety in Rust 24
Remove the move
• Line 3: error: value doesn’t live long enough
• Possible memory issues: UAF
• Spawn a thread
• Capture everything as a borrow
• The closure captures dst (mutable borrow)
• Rust infers how to capture dst
CS3211 L10 - Safety in Rust 25
Reference counted type (RC)
• Line 4: error: Rc<T> can’t be
sent across threads
• RC type is not atomically
managed
• No Send trait
CS3211 L10 - Safety in Rust 26
Traits
• Like an interface that you can implement for a given type
• It might have methods
• Example
• Clone
• Marker traits:
• Send – transferred across thread boundaries
• String, u32, Arc<String>
• Sync – safe to share references between threads
• Type T is Sync if and only if &T is Send
• Copy – safe to memcpy (for built-in types)
• u32, f32
• Not for Strings
CS3211 L10 - Safety in Rust 27
Atomic Reference Counted - Arc
• Arc: allows only shared references
• References cannot be mutated
• Line 3: move reference v (but the vector is a shared reference)
• Line 4: it’s safe to access v, but the vector cannot be modified
CS3211 L10 - Safety in Rust 28
Synchronization in Rust
• Shared memory
• Mutex
• Atomics
• Message-passing
• Channels: MPSC (multi-producer, single-consumer FIFO queue)
CS3211 L10 - Safety in Rust 29
Mutex • Based on ownership
• Data protected by the mutex
• Lock returns a guard - a proxy through which we can access the data
CS3211 L10 - Safety in Rust 30
Atomics
• Similar to modern C++
• Same memory model
• Ordering of memory operations – SeqCst, Relaxed, etc.
• Line 1: number is not mutable
• We mutate this through a shared reference
• Access the shared references through some ordering (memory model)
CS3211 L10 - Safety in Rust 31
Multi-Producer, Single-Consumer FIFO queue
• Channel with a reading and writing reference
• In the example: one reader and multiple writers
• Single consumer: we can’t clone the reading end of the channel
CS3211 L10 - Safety in Rust 32
Outline
• Challenges in concurrent programming
• Introducing Rust
• Safety, ownership and borrowing
• Concurrency in Rust
• Threads, Mutex, Atomic
• Libraries: Crossbeam, Rayon
CS3211 L10 - Safety in Rust 33
Crossbeam
• Ability to create scoped threads – now in std
• Scope is like a little container we are going to put our threads in
• You cannot borrow variables mutably into two threads in the same scope
• Message passing using multiple-producer-multiple-consumer channel
• With exponential backoff
CS3211 L10 - Safety in Rust 34
Scoped threads
• Line 5: create the scope
• Line 6: spawn threads
CS3211 L10 - Safety in Rust 35
Producer consumer
• Line 12: use a bounded channel
CS3211 L10 - Safety in Rust 36
Exponential backoff
• Resources might not be available right now?
• Retry later
• It’s unhelpful to have a tight loop that simply retries as fast as
possible
• Wait a little bit and try again
• If the error occurs, next time wait a little longer
• Rationale: if the resource is overloaded right now, the reaction of
requesting it more will make it even more overloaded and makes the
problem worse!
CS3211 L10 - Safety in Rust 37
Backoff with scoped threads
• Line 12: backoff in lock-free loop
• Line 20: wait for another thread to take its turn first
CS3211 L10 - Safety in Rust 38
Rayon
• A data parallelism library
• Parallelize some spots without full/major rewrite
• Similar in functionality with OpenMP
• But OpenMP uses compiler directives
• Rationale: reasonably common that computationally-intensive parts
of the program happen in a loop, so parallelizing loops is likely to be
quite profitable
CS3211 L10 - Safety in Rust 39
Example: Sequential maximum of a vector
CS3211 L10 - Safety in Rust 40
Example: Maximum of a vector with Rayon
CS3211 L10 - Safety in Rust 41
Rust in a nutshell
• Zero-cost abstraction (like C/C++)
• Memory safety & data-race freedom
• Results in
• Confident, productive systems programming
• References:
• https://www.youtube.com/watch?v=SiUBdUE7xnA
• https://www.youtube.com/watch?v=L0dEE2IqbD8
• https://www.youtube.com/watch?v=6BYKw0Y758Q
CS3211 L10 - Safety in Rust 43