Why Rust matters

Strong safety guarantees

High performance

  • Compiles to machine code (backed by LLVM)
  • No garbage collector (almost no runtime)

Batteries-included tooling

  • Package manager Cargo installed by default
  • Plugins for popular editors (Intellij, VsCode, ...)

High-quality ecosystem

High-quality documentation

Highly active community

At a glance

Some resources:

Safety Guarantees

Memory-safe References

(part 1: data races)

Let's talk about Data Races

A Data Race occurs when two pointers access the same memory at once, and at least one of those accesses is a mutation.

Two elements: Shared Memory and Mutable Memory

To eliminate data races, Rust has two reference types:

  • Shared immutable references (&T)
  • Exclusive mutable references (&mut T)
use std::thread;

fn main() {
    let mut counter = 0;

    thread::spawn(|| {
        for _ in 0..10 {
            counter += 1;
        }
    });

    loop {
        println!("Counter is {}!", counter);
    }
} // Compile-time error!

Let's look at some unsound code

If this were allowed to compile, it would cause undefined behavior

Rust shows us exactly why this isn't allowed to compile

Safety Guarantees

Memory-safe References

(part 2: automatic memory management)

Manual Memory

  • Programmer is responsible for cleaning heap memory
  • If you free memory one too few times, you get a memory leak
  • If you free memory one too many times, you get a double-free and segfault

Garbage Collection

  • Runtime is responsible for cleaning heap memory
  • Introduces the stop-the-world problem
  • Makes garbage-collected languages a poor fit for real-time or high-performance applications

Let's talk about memory management

Rust uses the Ownership automatic memory model

Imagine a function call-stack where every value belongs to a specific stack frame (the frame "owns" the value)

fn main

  • greeting = "Hello world!"
fn main() {
    let greeting = 
        String::from("Hello, world!");
}

Rust uses the Ownership automatic memory model

In Rust, a "call-by-value" is said to move the value into the called function

fn main

fn main() {
    let greeting = 
        String::from("Hello, world!");
        
    print_greeting(greeting);
}

fn print_greeting(greeting: String) {
    println!("{}", greeting);
}

fn print_greeting

  • greeting = "Hello world!"

Rust uses the Ownership automatic memory model

When a function returns, it drops any values in its scope

fn main

fn main() {
    let greeting = 
        String::from("Hello, world!");
        
    print_greeting(greeting);
    //             -------- value moved here
    println!("{}", greeting);
    //             ^^^^^^^^ value borrowed here after move
}

fn print_greeting(greeting: String) {
    println!("{}", greeting);
}

Rust uses the Ownership automatic memory model

This model correctly automatically cleans up memory when it goes out of scope

  • No risk of double-frees
  • No risk of forgetting to "drop" a value

What does this model lack so far?

  • All values seem to be consumed on first function call
  • How can we re-use values so we can pass them to multiple functions?

Safety Guarantees

Memory-safe References

(part 3: borrowing)

Let's talk about this Ownership metaphor

Consider ownership as the right to destroy something

  • If I own a book, I have the right to throw it away

Consider borrowing as the right to use something, but not to destroy it

  • If I lend you a book, you have the right to read it, but not the right to throw it away
  • I may tell you to only read it, not to write in it
  • When I lend you something, I eventually expect it back

Rust uses Borrowing to grant access without moving

Imagine giving a function temporary access to a resource

fn main

  • greeting = "Hello world!"
fn main() {
    let greeting = 
        String::from("Hello, world!");
        
    print_greeting(&greeting);
}

fn print_greeting(greeting: &str) {
    println!("{}", greeting);
}

fn print_greeting

  • greeting =

Rust uses Borrowing to grant access without moving

When we return from a function, borrowed resources remain where they were

fn main

  • greeting = "Hello world!"
fn main() {
    let greeting = 
        String::from("Hello, world!");
        
    print_greeting(&greeting);
    
    println!("{}", greeting); // OK!
}

fn print_greeting(greeting: &str) {
    println!("{}", greeting);
}

Data Structures

Structs

A struct is a conjunctive data type

struct Person {
    first_name: String,
    last_name: String,
}

impl Person {
    pub fn new(first_name: String, last_name: String) -> Person {
        Person {
            first_name: first_name,
            last_name: last_name,
        }
    }
    
    pub fn say_name(&self) {
        println!("Hi, my name is {} {}",
            self.first_name,
            self.last_name);
    }
}

Associated behavior is defined in an impl block

fn main() {
    let nick = Person::new(
        "Nick".to_string(),
        "Mosher".to_string());
    
    nick.say_name();         // Method syntax
    Person::say_name(&nick); // Associated function syntax
}

Functions that use some form of self can be called with method syntax

Data Structures

Enums

An enum is a disjunctive data type

enum Commute {
    Walk,
    Bike,
    Bus,
    Car,
}

fn main() {
    let my_commute = Commute::Walk;
    match my_hometown {
        Commute::Walk => println!("I commute by walking!"),
        Commute::Bike => println!("I commute by biking!"),
        Commute::Bus => println!("I commute on the Bus!"),
        Commute::Car => println!("I commute by Car!"),
    }
}

An enum value is exactly one of the variants. You check which one it is by pattern matching

An enum forces exhaustive patterns

enum Commute {
    Walk,
    Bike,
    Bus,
    Car,
}

fn main() {
    let my_commute = Commute::Walk;
    match my_commute {
    //    ^^^^^^^^^^ patterns `Bike`, `Bus` and `Car` not covered
        Commute::Walk => println!("I commute by walking!"),
    }
    
    match my_commute {
        Commute::Walk => println!("I commute by walking!"),
        _ => println!("I commute some other way"), // Catch-all
    }
}
enum Commute { Walk, Bike, Train { have_bike: bool } }
enum CommuteEvent { GetOnBike, GetOnTrain, LeaveVehicle }

impl Commute {
    pub fn change_commute(&mut self, event: CommuteEvent) {
        use {Commute::*, CommuteEvent::*};
        match (&self, event) {
            (Walk, GetOnBike) => *self = Bike,
            (Walk, GetOnTrain) => *self = Train { have_bike: false },
            (Walk, LeaveVehicle) => println!("You're already walking!"),
            (Bike, GetOnBike) => println!("You're already on a bike!"),
            (Bike, GetOnTrain) => *self = Train { have_bike: true },
            (Bike, LeaveVehicle) => *self = Walk,
            (Train { .. }, GetOnTrain) =>
                println!("You're already on the train!"),
            (Train { .. }, GetOnBike) =>
                println!("You can't get on your bike on the train!"),
            (Train { have_bike }, LeaveVehicle) if *have_bike => *self = Bike,
            (Train { have_bike }, LeaveVehicle) if !*have_bike => *self = Walk,
            _ => unreachable!(),
        }
    }
}

Example making a state machine with enums as state

Strong safety guarantees

More compile-time rules mean fewer runtime errors

  • "If it compiles, it's probably right"

No such thing as Null. No exceptions

Prevents race conditions via ownership and borrowing rules

Strong safety guarantees: pattern matching

Enums are a closed set of possible variants

let colors = vec!["red", "green", "blue"];

let third: Option<&str> = colors.get(2);
match third {
    Some(color) => println!("The color is {}!", color),
    None => println!("There was no color"),
}

let fourth: Option<&str> = colors.get(3);
match fourth {
    Some(color) => println!("The color is {}!", color),
} // Compile Error! Non-exhautive match
// An Option represents the possibility of absence
pub enum Option<T> {
    None,
    Some(T),
}

Strong safety guarantees: fearless concurrency

Mutable references must be exclusive

use std::thread;

fn main() {
    let mut counter = 0;

    thread::spawn(|| {
        for _ in 0..10 {
            counter += 1;
        }
    });

    loop {
        println!("Counter is {}!", counter);
    }
} // Compile-time error!

High performance, low overhead

Rust follows the idea of zero-cost abstractions

Can use "unsafe" Rust to get lower-level control

No garbage collector

High performance, low overhead

ripgrep: A recursive text-searching tool like grep

  • Usually faster than grep

actix/actix-web: Actor system and web framework

tantivy/toshi: Search services similar to elasticsearch

Batteries-included tooling: Cargo

How to install Rust and start a new project:

# Install Rust
$ curl https://sh.rustup.rs -sSf | sh

# Create a new project
$ cargo new --bin hello_world
$ cd hello_world
$ ls -la
drwxr-xr-x   9 user  group  288 Jan 31 14:10 .git
-rw-r--r--   1 user  group   19 Jan 31 14:09 .gitignore
-rw-r--r--   1 user  group  133 Jan 31 14:09 Cargo.toml
drwxr-xr-x   3 user  group   96 Jan 31 14:09 src

# Run main
$ cargo run
Hello, world!

Helpful compiler messages

// src/main.rs
fn main() {
    let greeting: String = "Hello, world!";
    println!("My greeting is {}", greeting);
}

High quality ecosystem

rayon: A safe parallel processing library

crossbeam: Fast concurrency primitives

tokio: Asynchronous IO using Futures

serde: Serialization and deserialization library

More examples at awesome-rust

diesel: ORM and query builder for safe database access

Example project: Dialogflow bot

Dialogflow is a natural-language processing service that uses webhooks to send intents to a custom-built API

https://dialogflow.com/docs/fulfillment/how-it-works

Showcases:

  • Http request handling
  • Json serialization/deserialization
  • Error handling

Why Rust matters

By Nick Mosher

Why Rust matters

  • 189
Loading comments...

More from Nick Mosher