Stack vs. Heap
Stack is cleaned at the end of function. Instructions for cleaning heap are in Rust added by compiler.
Pointer vs. Smart Pointer
Pointer - object that stores a memory address, references a location in memory
Smart pointer - simulates a pointer while storing other metadata and providing added features
Smart pointers implement Deref and Drop traits.
Smart pointers
Box
fn main() {
let b = Box::new(5);
// uses 'Deref coercion' - converts a type into a reference to another type
println!("b = {}", b);
}
fn main() {
let x = 5;
let y = Box::new(x);
assert_eq!(5, x);
assert_eq!(5, *y);
}
Drop
struct CustomSmartPointer {
data: String,
}
impl Drop for CustomSmartPointer {
fn drop(&mut self) {
println!("Dropping CustomSmartPointer with data `{}`!", self.data);
}
}
fn main() {
let c = CustomSmartPointer {
data: String::from("my stuff"),
};
let d = CustomSmartPointer {
data: String::from("other stuff"),
};
println!("CustomSmartPointers created.");
}
We can drop an object manually:
fn main() {
let c = CustomSmartPointer {
data: String::from("some data"),
};
println!("CustomSmartPointer created.");
drop(c);
println!("CustomSmartPointer dropped before the end of main.");
}
Rc - Reference Counted Smart Pointer
When we need multiple owners of an object (graphs - node cannot be released as long another node has an edge to it).
Lisp-like list:
enum List {
Cons(i32, Rc<List>),
Nil,
}
use crate::List::{Cons, Nil};
use std::rc::Rc;
fn main() {
let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
let b = Cons(3, Rc::clone(&a));
let c = Cons(4, Rc::clone(&a));
}
RefCell
Only one owner, unlike Box, the ownership is checked at runtime.
pub trait Messenger {
fn send(&self, msg: &str);
}
pub struct LimitTracker<'a, T: Messenger> {
messenger: &'a T,
value: usize,
max: usize,
}
impl<'a, T> LimitTracker<'a, T>
where
T: Messenger,
{
pub fn new(messenger: &T, max: usize) -> LimitTracker<T> {
LimitTracker {
messenger,
value: 0,
max,
}
}
pub fn set_value(&mut self, value: usize) {
self.value = value;
let percentage_of_max = self.value as f64 / self.max as f64;
if percentage_of_max >= 1.0 {
self.messenger.send("Error: You are over your quota!");
} else if percentage_of_max >= 0.9 {
self.messenger
.send("Urgent warning: You've used up over 90% of your quota!");
} else if percentage_of_max >= 0.75 {
self.messenger
.send("Warning: You've used up over 75% of your quota!");
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::cell::RefCell;
struct MockMessenger {
sent_messages: RefCell<Vec<String>>,
}
impl MockMessenger {
fn new() -> MockMessenger {
MockMessenger {
sent_messages: RefCell::new(vec![]),
}
}
}
impl Messenger for MockMessenger {
fn send(&self, message: &str) {
self.sent_messages.borrow_mut().push(String::from(message));
}
}
#[test]
fn it_sends_an_over_75_percent_warning_message() {
let mock_messenger = MockMessenger::new();
let mut limit_tracker = LimitTracker::new(&mock_messenger, 100);
limit_tracker.set_value(80);
assert_eq!(mock_messenger.sent_messages.borrow().len(), 1);
}
}
Threads
use std::thread;
use std::time::Duration;
fn main() {
thread::spawn(|| {
for i in 1..10 {
println!("hi number {} from the spawned thread!", i);
thread::sleep(Duration::from_millis(1));
}
});
for i in 1..5 {
println!("hi number {} from the main thread!", i);
thread::sleep(Duration::from_millis(1));
}
}
Join threads
use std::thread;
use std::time::Duration;
fn main() {
let handle = thread::spawn(|| {
for i in 1..10 {
println!("hi number {} from the spawned thread!", i);
thread::sleep(Duration::from_millis(1));
}
});
for i in 1..5 {
println!("hi number {} from the main thread!", i);
thread::sleep(Duration::from_millis(1));
}
handle.join().unwrap();
}
Transfer data between threads
use std::sync::mpsc;
use std::thread;
fn main() {
let (sender, receiver) = mpsc::channel();
thread::spawn(move || {
let val = String::from("hi");
sender.send(val).unwrap();
});
let received = receiver.recv().unwrap();
println!("Got: {}", received);
}
Mutex
use std::sync::Mutex;
fn main() {
let m = Mutex::new(5);
{
let mut num = m.lock().unwrap();
*num = 6;
}
println!("m = {:?}", m);
}
Arc - Atomically Reference Counted
A thread-safe reference-counting pointer.
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..10 {
let counter = Arc::clone(&counter);
let handle = thread::spawn(move || {
let mut num = counter.lock().unwrap();
*num += 1;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("Result: {}", *counter.lock().unwrap());
}
Rayon library
[dependencies]
rayon = "1.4"
use rayon::prelude::*;
use std::borrow::Borrow;
/// Computes product of elements in vector in parallel
fn product_of_vec(input: &[i32]) -> i32 {
input.par_iter()
.map(|&i| i * i)
.sum()
}
fn main() {
let product = product_of_vec(vec![2, 4, 6].borrow());
println!("Product is {}", product);
}
Combined with channel
use std::sync::mpsc::channel;
use rayon::prelude::*;
fn main() {
let (sender, receiver) = channel();
(0..5).into_par_iter().for_each_with(sender, |s, x| s.send(x).unwrap());
let mut res: Vec<_> = receiver.iter().collect();
res.sort();
assert_eq!(&res[..], &[0, 1, 2, 3, 4])
}
Exercises
Dining philosophers
Introduction
In computer science, the dining philosophers problem is an example problem often used in concurrent algorithm design to illustrate synchronization issues and techniques for resolving them.
You can read more about this problem here.
Task
- there are 5 philosophers
- there are 5 sticks (represented as mutexes with default value 0)
- for each philosopher, create a new thread, where:
- the philosopher tries to lock left and right stick
- after he has both sticks, print that he is eating
- sleep for a random number of milliseconds
- print that he stopped eating, increment mutex (counter of how many times each fork has been used)
- release lock on both sticks
- print that he is thinking
- sleep for a random number of milliseconds
- print that he stopped thinking
- join all threads
- print how many times each stick has been used (should be 20)
Bonus
- use
std::sync::mpsc::channel
to collect all messages first - at the end, print all messages
Solution
Check out my learning Rust repo on GitHub!
Top comments (0)