Rust

Fast, safe and productive — pick three

First user group Meetup in Leipzig (2016-10-20)

Common system programming problems:

  • Undefined behaviors
  • Iterator invalidation
  • Data races

Undefined behaviors


int arr[4] = {0, 1, 2, 3};
int *p = arr + 5; // undefined behavior
                        

int k, i;
for (i = 0; i < 10; i++) {
    k = k + 1; // What is k?
}
                        

Iterator invalidation


vector<int> x = { 1, 2, 3 };
int j = 0;
for (auto it = x.begin(); it != x.end(); ++it) {
    x.push_back(j); // Makes iterator invalid.
    j++;
    cout << j << " .. ";
}
                        
Iterators are invalidated by some operations that modify a std::vector.

Data races


if (x == 5) { // The "Check"

   y = x * 2; // The "Act"

   // If another thread changed x
   // in between "if (x == 5)" and
   // "y = x * 2" above, y will not be 
   // equal to 10.
}
                        

Why is this a problem?

  • Runtime failures like segfaults are not recoverable.
  • Extremely common source of security bugs.
  • Existing solutions for safety are slow and usually require a garbage collection.
  • Non type safety leads into common pitfalls (void pointers, wrong casts).
  • Code Review for such corner cases slows down the development.

Some basics

If, loops, functions


if a > 1 {
    println!("{:?}", a);
}

let k = 0; // u32

let mut i: i32 = 0; // Same: let mut i = 0i32;

loop {
    if i > 10 { break; }
    i += 1;
    println!("{:?}", i);
}

fn multiply(x: i32, y: i32) -> i32 {
    x * y
}
                        

Expression based language


let b = 3i32;

let a = if b > 1 {
    let mut c = b - 5;
    c = c * b;
    c
} else {
    1i32
};

fn greater(a: i32, b: i32) -> i32 {
    if a > b {
        a
    } else {
        b
    }
}
                        

Pattern matching


let number = 5u32;
let size = match number {
    0 => "none",
    2 | 3 => "tiny",
    4...7 => "small",
    8...20 => "medium",
    _ => "large"
};

match number {
    2 | 8 | 9 => {
      let mut c = 1;
      c += number;
      println!("The incremented number is {}", c);
    },
    _ => println!("Sorry! :(")
}
                        

Destructuring


let pair = (4u32, 5u32);
let (a, b) = pair;
let (b, a) = (a, b);
let smaller = match pair {
    (x, y) if x < y => x,
    (_, y) => y
};

match pair {
    (0, 0) => println!("Origin"),
    (0, y) => println!("Y-axis, coordinate {}", y),
    (x, 0) => println!("X-axis, coordinate {}", x),
    (x, y) => {
      let distance = ((x*x + y*y) as f32).sqrt();
      println!("Point is ({}, {}), and {} units from origin",
               x, y, distance);
    }
};
                        

Data types

Structures


struct Point {
    x: i32,
    y: i32
}

let p = Point {x: 0, y: 0};
println!("X coordinate is {}", p.x)
                        

Enums


enum Shape {
    Circle(Point, u32),
    Rectangle(Point, Point),
}

enum Option<T> {
    Some(T),
    None,
}

let origin = Point {x: 0, y: 0};
let circ = Shape::Circle(origin, 10);
                        

Extended pattern matching


let perimeter = match shape {
    Circle(_, r) => 2*pi*r,
    Rectangle(p1, p2) => 2*abs(p2.x - p1.x) + 2*abs(p2.y - p1.y)
}

let perimeter = match shape {
    Circle(_, r) => 2*pi*r,
    Rectangle(Point {x: x1, y: y1}, Point {x: x2, y: y2}) =>
      2*abs(x2 - x1) + 2*abs(y2 - y1)
}

match point {
    Point {x: 2...6, y: -1...5} => println!("I like this point"),
    _ => println!("I do not like this point"),
}
                        

Generics


enum Option<T> {
    Some(T),
    None
}

fn maybe_sqrt(x: i32) -> Option<u32> {
    if x >= 0 {
      Some(sqrt(x) as u32)
    } else {
      None
    }
}
                        

Implementations


impl Point {
    fn distance(&self) -> f32 { // point.distance()
        ((self.x*self.x + self.y*self.y) as f32).sqrt()
    }

    fn random() -> Point { // Point::random()
      Point {
        x: 4, // Chosen by fair dice roll
        y: 4,  // Guaranteed to be random
      }
    }
}

                        

Traits


trait Pointy {
    fn poke(&self, at: &str);

    fn hello(&self) {
        println!("Hello world");
    }
}

impl Pointy for Point {
    fn poke(&self, at: &str) {
        println!("Poked {}", at);
    }
}

fn poke_forever<T>(pointy: T, at: &str) where T: Pointy {
    loop {
        pointy.poke(at)
    }
}
                        

Memory management

Moves and copies


let x = 5i8;
let y = x;
println!("x is {:?}", x); // y was copied from x

let x = vec![1u8, 2u8, 3u8];
let y = x; // the vector was "moved"
println!("y is {:?}", y);
// Will not work, the vector is "owned" by y
// println!("x is {}", x)

fn abc(x: Vec<u8>) {
    // do something
}
let vec = vec![1u8, 2u8, 3u8];
abc(vec); // 'abc' consumes vec
                        

Borrowing


let x = vec![1, 2, 3];
let y = &x; // the vector was "borrowed"
let c = x.clone(); // Explicit copy
println!("x is {:?}", x);
println!("y is {:?}", *y);

fn abc(x: &Vec<u8>) {
    // do something
}

let vec = vec![1u8,2u8,3u8];
abc(&vec); // Passes a borrowed reference
// Still can use vec here
                        

Borrowing & mutability


let mut x = vec![1u8, 2u8, 3u8];
{
    let y = &x; // the pointer was "borrowed"
    // Not allowed, x is currently borrowed and cannot be mutated
    // x.push(1);
    // Not allowed, y is not a mutable reference
    // y.push(1);
}
x.push(1); // The borrow was "returned", we can mutate now

let mut x = vec![1u8, 2u8, 3u8];
{
    let y = &mut x; // the pointer was "borrowed", mutably
    // Still not allowed, x is currently borrowed and cannot be mutated
    // x.push(1);
    // also not allowed, y is mutating this
    // println!("x is {}", x) 
    // Allowed, y is a mutable reference
    y.push(1); 
}
x.push(1) // The borrow was "returned", we can mutate now
                        

Ownership and implementations


struct Polygon {points: Vec<Point>};
impl Point {
    // This one moves
    fn draw_move(self) {
        // ...
    }

    // This one borrows
    fn draw_borrow(&self) {
        // ...
        // after calling p.draw_borrow() I can still use p
    }

    // This one borrows mutably
    fn draw_borrow_mut(&mut self) {
        // ...
        // I can mutate self here
    }
}

// (*polygon).draw_borrow() dereference not necessary
                        

The heap


let mut x = Box::new(1); // On the heap
*x = 2;

// Type Box<u32>
// Also gets moved, not copied

fn abc() {
    let x = Box::new(1); // malloc() happened
    // do stuff with x or *x
    // free() happened
}

fn def() -> Box<u32>{
    let x = Box::new(1); // malloc() happened
    // do stuff with x or *x
    x // x returned to outer owner  
}
                        

Destructuring and binding by reference


let pair = (Box::new(1), Box::new(2));
let (a, b) = pair;
// The boxes were moved out of `pair`, cannot use it anymore

let pair = (Box::new(1), Box::new(2));
{
    let (ref a, ref b) = pair;
    // a, b are borrowed references now, so everything is fine
    // use `ref mut` for mutable references
}

// Works in match statements too!
let maybe_heap = Some(Box::new(1));
match maybe_heap {
    Some(ref x) => println!("{}", x),
    None => println!("No variable")
}
                        

Strings and arrays


let mut fixed_len_vec = [1,2,3]; // type [u32, 3]
fixed_len_vec[1] = 2;

let mut buffer = vec![1,2,3]; // type Vec<u32>
buffer.push(20); // Now it is of length 4!
let slice = &buffer[0..2]; // type &[u32]
let slice = &[1,2,3]; // Same

let owned = "Manish".to_string(); // type String
let static_slice = "Manish"; // type `&'static str`
let slice = &owned[0..3]; // type &str
                        

Some unsafe Rust


extern crate core;
use std::{mem, ptr};

fn main() {
    let y = *dangle() + 1;
    // Segfault.
}

fn dangle() -> Box<u32> {
    unsafe {
        // Null pointer
        let mut p: *mut int = ptr::null_mut();
        // Heap memory, will be freed at the end of this function
        let b = Box::new(1u32);
        ptr::write(p, *b);
        mem::transmute(p) // Converts to box and returns
    }
}
                        

Concurrency

Thread safety #1


use std::sync::mpsc::*;
use std::thread::spawn;

let (tx, rx) = channel();

spawn(move || {
    tx.send(1); // works!
});

let x = rx.recv();
                        

Thread safety #2


use std::sync::mpsc::*;
use std::thread::spawn;

let (tx, rx) = channel();

spawn(move || {
    let x = Box::new(1);
    tx.send(x); // works!
});

let x = rx.recv();
                        

Thread safety #3


use std::sync::mpsc::*;
use std::thread::spawn;
let (tx, rx) = channel();

spawn(move || {
    let x = Box::new(1);
    tx.send(&x); // x does not live long enough
});

let x = rx.recv();
                        

Thread safety #4


use std::sync::mpsc::*;
use std::thread::spawn;
use std::rc::Rc;
// use std::sync::Arc;

let (tx, rx) = channel();

spawn(move || {
    let x = Rc::new(1); // smart pointer
    tx.send(x.clone()); // cannot send between thread safely
});

let x = rx.recv();
                        

More features

Further topics #1

  • General thread safety due to borrow checker
  • Powerful zero cost abstractions without garbage collection
  • Error handling via result types
  • Associated types and constants
  • Buddy traits, associated trait types, trait objects vs generics
  • Error Handling
  • Lifetimes
  • Documentation

Further topics #2

  • Iterators
  • Closures
  • Futures
  • I/O Streams
  • Macros
  • Unsafe Code
  • Program Structure, Modules, Crates
  • External crates

Resources

We have a blog.

Two good books, one free:

Thank you!