DEV Community

Cover image for Rust 5 - Smart pointers, parallel programming, rayon
Petr Janik
Petr Janik

Posted on

Rust 5 - Smart pointers, parallel programming, rayon

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);
}
Enter fullscreen mode Exit fullscreen mode
fn main() {
    let x = 5;
    let y = Box::new(x);

    assert_eq!(5, x);
    assert_eq!(5, *y);
}
Enter fullscreen mode Exit fullscreen mode

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.");
}
Enter fullscreen mode Exit fullscreen mode

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.");
}
Enter fullscreen mode Exit fullscreen mode

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));
}
Enter fullscreen mode Exit fullscreen mode

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

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));
    }
}
Enter fullscreen mode Exit fullscreen mode

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();
}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

Mutex

use std::sync::Mutex;

fn main() {
    let m = Mutex::new(5);

    {
        let mut num = m.lock().unwrap();
        *num = 6;
    }

    println!("m = {:?}", m);
}
Enter fullscreen mode Exit fullscreen mode

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());
}
Enter fullscreen mode Exit fullscreen mode

Rayon library

[dependencies]
rayon = "1.4"
Enter fullscreen mode Exit fullscreen mode
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);
}
Enter fullscreen mode Exit fullscreen mode

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])
}
Enter fullscreen mode Exit fullscreen mode

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

Dining philosophers


Check out my learning Rust repo on GitHub!

Top comments (0)