Rust 5 - Smart pointers, parallel programming, rayon
Tags: rust
Published: 2020-09-01 06:54:18

## 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<T>

```rust
fn main() {
    let b = Box::new(5);
    // uses 'Deref coercion' - converts a type into a reference to another type
    println!("b = {}", b);
}
```

```rust
fn main() {
    let x = 5;
    let y = Box::new(x);

    assert_eq!(5, x);
    assert_eq!(5, *y);
}
```

### Drop

```rust
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:

```rust
fn main() {
    let c = CustomSmartPointer {
        data: String::from("some data"),
    };
    println!("CustomSmartPointer created.");
    drop(c);
    println!("CustomSmartPointer dropped before the end of main.");
}
```

## Rc<T> - 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:*

```rust
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<T>

Only one owner, unlike Box<T>, the ownership is checked at runtime.

```rust
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

```rust
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

```rust
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

```rust
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

```rust
use std::sync::Mutex;

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

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

    println!("m = {:?}", m);
}
```

## Arc<T> - Atomically Reference Counted

A thread-safe reference-counting pointer.

```rust
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

```rust
[dependencies]
rayon = "1.4"
```

```rust
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

```rust
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](https://en.wikipedia.org/wiki/Dining_philosophers_problem).

### 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](https://github.com/petr7555/Learning-Rust/tree/master/05/dining_philosophers)

---

Check out my learning Rust repo on GitHub!
{% github petr7555/Learning-Rust/ no-readme %}