Getting Started With Rust

This is a tutorial for Parallel and Distributed Computing Fall 2016, using Rust.

First, watch this and be inspired:

For this homework assignment we will be using a Rust playground found here to compile and run online so you don’t have to download and install Rust.

Basic Rust

You should first be brought to a simple Hello World program written in Rust. It uses fn main(){} as its main function and println!(); to print its output. Try running it!

Do This:

  1. Create a variable using the binding ‘let’ and set it equal to the string “hello Rab”. Print this out using println!(“{}”, name of your variable);

Example: let x = "hello";

Try This:

  1. Below your string “hello Rab” reassign the string to “computer? i barely know her”

You may have noticed you got an error. This is because bindings in Rust are by default immutable, so the code will not compile. The primary reason for this default state is for code safety, meaning its function is to let the programmer know that there is something that mutated that may not have been intended to. This can be fixed by simply putting mut (mutable) after the binding let

Try This:

  1. Put mut after the let binding and look at the results

You’ll notice this runs but gives you a warning for the unused string “hello rab”

Threads in Rust

Rust’s standard library provides the use of a thread library which allows you to run code in parallel.

Rab.gif

One method thread::spawn() accepts a closure, which is executed in a different thread. This is similar to the lambda captures which we did in C++.

In the example above the spawn returns a handle to the thread, which is used to wait for the child thread to finish and then extracts the results.

Let’s take a look at another example of how a threaded program works in Rust:

Here as you can see, the child.join() method waits for each thread to finish before returning a result, which helps avoid errors.

Do This:

  1. Enter the code above into the Rust playground and look at the output you get.

You should get something like this:

Now we know what you’re wondering…How could Rust help with dealing with race conditions?

Take a look here

If you ran this in C++, this will produce a race condition where the value of data[0] will be different each time because each thread is changing it at the same time. Rust will not allow you to do this.

Do This:

  1. Run the code.
  2. Does it give you an error?

Rust can capture this because of its ownership system. Each thread moves ownership of data to itself, but you can’t have more than one owner. You can read more about the ownership system here, as well as a discussion on how to fix the code above to it compiles and does not have a race condition.

Dining Philosophers

Why don’t we help some philosophers eat!

Here is a non parallelized version of the dining philosophers problem in Rust.

use std::thread;
struct Philosopher {
name: String,
}
impl Philosopher {
fn new(name: &str) -> Philosopher {
Philosopher {
name: name.to_string(),
}
}
    fn eat(&self) {
println!("{} is eating.", self.name);
        thread::sleep_ms(1000);
        println!("{} is done eating.", self.name);
}
}
fn main() {
let philosophers = vec![
Philosopher::new("Judith Butler"),
Philosopher::new("Gilles Deleuze"),
Philosopher::new("Karl Marx"),
Philosopher::new("Emma Goldman"),
Philosopher::new("Michel Foucault"),
];
for p in &philosophers {
p.eat();
}
}

Now let’s familiarize ourselves with the parallelized version of the Dining Philosophers problem in Rust.

use std::thread; 
use std::sync::{Mutex, Arc};
struct Philosopher {     
name: String,
left: usize,
right: usize,
}
impl Philosopher {     
fn new(name: &str, left: usize, right: usize) -> Philosopher {
Philosopher {
name: name.to_string(),
left: left,
right: right,
}
}
    fn eat(&self, table: &Table) {
let _left = table.forks[self.left].lock().unwrap();
println!("{} picked up fork {}", self.name,self.left);
thread::sleep_ms(200);
        let _right = table.forks[self.right].lock().unwrap();
println!("{} picked up fork {}", self.name,self.right);
        println!("{} is eating.", self.name);
        thread::sleep_ms(1000);
println!("{} is done eating.", self.name);
}
}
struct Table {
forks: Vec<Mutex<()>>,
}
fn main() {
let table = Arc::new(Table { forks: vec![
Mutex::new(()),
Mutex::new(()),
Mutex::new(()),
Mutex::new(()),
Mutex::new(()),
]});
    let philosophers = vec![
Philosopher::new("Judith Butler", 0, 1),
Philosopher::new("Gilles Deleuze", 1, 2),
Philosopher::new("Karl Marx", 2, 3),
Philosopher::new("Emma Goldman", 3, 4),
Philosopher::new("Michel Foucault", 4, 0),
];
    let handles: Vec<_> = philosophers.into_iter().map(|p| {
let table = table.clone();
        thread::spawn(move || {
p.eat(&table);
})
}).collect();
    for h in handles {
h.join().unwrap();
}
}

For information on the Mutex in the code check this documentation out.

Try This:

  1. Copy this code into the Rust playground and run it.
  2. What happens?

If you said “It deadlocks” you would be correct!

The output should be something like this:

Judith Butler picked up fork 0 
Gilles Deleuze picked up fork 1
Karl Marx picked up fork 2
Emma Goldman picked up fork 3
Michel Foucault picked up fork 4

In this scenario all of the philosophers pick up their right forks and everyone gets stuck.

Do This:

  1. Find a way to fix this. (hint: what happens if you make one of the philosophers left handed?)
  2. When you have solved it, try to explain why it works.
Like what you read? Give Samir Bekhechi a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.