Rust Intro

Felix and the Rust Team

25 June 2015; http://bit.ly/1N93I0P

Rust: What? Why? (How?)

Goals for today

  • Convince you Rust is awesome
  • Provide seeds of knowledge
    • One can only cultivate so much during a three hour window

These slides

http://bit.ly/1N93I0P

~

http://pnkfelix.github.io/cyot/tutorial/slides/whistler_rust_intro.html

Goals for Rust

  • Safe. Concurrent. Fast.

  • Specifics
    • Abstraction without overhead
    • Memory safety without garbage collection
    • Concurrency without data races
  • Generalization: HACK WITHOUT FEAR!

A taste

  • Three fast "amuse bouches"
    • not the main course
    • (not even an appetizer)

Abstraction without overhead

The below loop demo compiles down to tight code:

// sums all the positive values in `v`
fn sum_pos(v: &Vec<i32>) -> i32 {
    let mut sum = 0;
    for i in v.iter().filter(|i| **i > 0) {
        sum += *i;
    }
    sum
}

Abstraction without overhead

Generated x86_64 machine code for fn sum_pos:

    leaq    (%rdi,%rsi,4), %rcx
    xorl    %eax, %eax
    jmp .LBB5_1
.LBB5_3:
    addl    %edx, %eax
    .align  16, 0x90
.LBB5_1:
    cmpq    %rdi, %rcx
    je  .LBB5_4
    movl    (%rdi), %edx
    addq    $4, %rdi
    testl   %edx, %edx
    jle .LBB5_1
    jmp .LBB5_3
.LBB5_4:
    retq

(when compiled in "release mode")

Memory safety

Example: catches iterator invalidation bugs

fn this_wont_compile(v: &mut Vec<i32>) -> i32 {
    let mut sum = 0;
    for &i in v.iter() {
        sum += i;
        if i > 0 { v.push(0); }
        //         ~~~~~~~~~ invalid! (might realloc
        //                   the backing storage for `v`)
    }
    sum
}
error: cannot borrow `*v` as mutable because it is also borrowed
       as immutable
        if i > 0 { v.push(0); }
                   ^
note: previous borrow of `*v` occurs here; the immutable borrow
      prevents subsequent moves or mutable borrows of `*v` until
      the borrow ends
    for &i in v.iter() {
              ^

Slick, Fearless Concurrency

See also Fearless Concurrency blog post.

use std::thread;
fn par_max(data: &[u8]) -> u8 {
    if data.len() <= 4 { return seq_max(data); }
    let len_4 = data.len() / 4; // DATA = [A .., B .., C .., D..]
    let (q1, rest) = data.split_at(len_4);    // (A.. \ B..C..D..)
    let (q2, rest) = rest.split_at(len_4);    //  (B.. \ C..D..)
    let (q3, q4)   = rest.split_at(len_4);    //   (C.. \ D..)
    let t1 = thread::scoped(|| seq_max(q1));  // fork A..
    let t2 = thread::scoped(|| seq_max(q2));  // fork B..
    let t3 = thread::scoped(|| seq_max(q3));  // fork C..
    let v4 = seq_max(q4);                     // compute D..
    let (v1, v2, v3) = (t1.join(), t2.join(), t3.join()); // join!
    return seq_max(&[v1, v2, v3, v4]);
}

(caveat: above is using unstable thread::scoped API)

Part 1: Play

Language and API docs

Playpen

  • For later: Install Rust from the USB stick when it comes to you

Outline for Tutorial

  • Goals
  • Ownership and Borrowing; Arrays and Strings
  • Local Development: Cargo; Crates and Modules
  • More Fundamentals: Data; More on Borrowing; Traits
  • Systems Development: Concurrency and I/O; FFI

Lets dive in

pub fn main() {
    println!("Hello World!"); // (`foo!` means macro)
    print_ten();                                               
}

fn print_ten() {
    println!("Ten: {}", 10);
    //             ~~   ~~
    //             |     | 
    //    placeholder   argument
}

A really big idea

Ownership and Move Semantics

Creation and Consumption

Once initialized, local owns its data (vector's backing store)

#[test]
pub fn create_owned() {
    let mut vec = Vec::new();         //  + (`vec` initialized)
    vec.push(2000);                   //  |   ... and
    vec.push( 400);                   //  |        also
    vec.push(  60);                   //  |         modified ...
    println!("vec: {:?}", vec);       //  |
    let the_sum = sum(vec);           // (... and moved)
    println!("the_sum: {}", the_sum); 
}

At scope end, initialized variables are cleaned up

fn sum(v: Vec<i32>) -> i32 {          //  +
   let mut accum = 0;                 //  |
   for i in v.iter() { accum += *i; } //  |
   accum // (p.s. where is `return`?) //  |
}                                     //  + (`v` destroyed/freed)

Move vs Copy

#[test]
fn demo_owned_vs_copied() {
    let moving_value = vec![1, 2, 3];
    let copied_value = 17;
    let tuple = (moving_value, copied_value);

    println!("copied_value: {:?}", copied_value);
    println!("moving_value: {:?}", moving_value);
}
error: use of moved value: `moving_value` [E0382]
    println!("moving_value: {:?}", moving_value);
                                   ^~~~~~~~~~~~

note: `moving_value` moved here because it has type
      `collections::vec::Vec<i32>`, which is non-copyable
    let tuple = (moving_value, copied_value);
                 ^~~~~~~~~~~~

Exercises

Exercises 1

http://pnkfelix.github.io/cyot/tutorial/exercises/ex_part_1.html

Borrowing

Moves insufficient on their own

Imagine programming without reuse

#[test]
fn moves_insufficient() {
    let vec = expensive_vector_computation();

    let result1 = some_vec_calculation(vec); // <-- `vec` moved here

    let result2 = other_calculation(vec); // oops, `vec` is *gone*

    combine(result1, result2);

}
error: use of moved value: `vec` [E0382]
    let result2 = other_calculation(vec); // oops
                                    ^~~
note: `vec` moved here because it has type
      `collections::vec::Vec<i32>`, which is non-copyable
    let result1 = some_vec_calculation(vec); // <-- `vec` moved here
                                       ^~~

Want: access to owned data without consuming it

Thus, "borrowing"

#[test]
fn moves_insufficient() {
    let vec = expensive_vector_computation();

    let result1 = some_vec_calculation(&vec); // <-- lend out `vec`

    let result2 = other_calculation(&vec); // <-- lend again, no prob

    combine(result1, result2);

} // (`vec` is destroyed/freed aka "dropped" here)
                                    &vec
                                    ~~~~
                                      |
                              a borrow expression

("mo' features, mo' problems")

Big Question

  • Why are safety violations generally hard to detect?
  • It is due to aliasing

  • Borrows reintroduce aliasing

Q: How to ensure safety in presence of aliasing?

A: Restrict the aliasing

Simple metaphor: RW lock

  • Read-only operations do not require exclusive access

  • Exclusive access requires there are no other readers

Rust uses analogous model (at compile-time) for borrows

Borrowing: Basic Mental Model

  • Base types T
    • e.g. char, Vec<i32>
    • If type copyable, then you can always copy it
    • You can move it only if no borrow active
  • Immutable borrows: &T
    • "Read-only." Freely aliasable; copyable
    • (i.e. "many readers")
  • Mutable borrows: &mut T
    • Read/Write. Exclusive access; non-copy
    • (i.e. "at most one writer")

Immutable borrows

Borrowing (immutably)

#[test]
fn show_some_borrows() {

    let v1 = vec![1, 2, 3];
    let v2 = vec![4, 5, 6];

    let r1 = &v1;
    let r2 = &v2;
    foo(r1);
    foo(r2);

}
fn foo<'a>(v: &'a Vec<i32>) { println!("v[1]: {}", v[1]); }

&v1 and &v2 are borrowing v1 and v2.

Scopes and Lifetimes

#[test]
fn show_some_lifetimes() {

    let v1 = vec![1, 2, 3]; //                 +
    let v2 = vec![4, 5, 6]; //            +    |
                            //            |    |
    let r1 = &v1;           //       +    |    |
    let r2 = &v2;           //  +    |    |    |
    foo(r1);                //  |    |    |    |  
    foo(r2);                // 'r2  'r1  'v2  'v1
                            //  |    |    |    | 
}                           //  +    +    +    +
fn foo<'a>(v: &'a Vec<i32>) { println!("v[1]: {}", v[1]); }

Each borrow selects "appropriate" lifetime 'a.

Borrow Checking Prevents Errors

fn borrow_checking_prevents_errors() {

    let v1 = vec![1, 2, 3];      //        +
                                 //        |
    let r1 = &v1;                //  +    'v1
                                 //  |     |
    consume(v1);                 // 'r1   (moved)
    foo(r1);                     //  |
}                                //  +
    fn foo<'a>(v: &'a Vec<i32>) { println!("v[1]: {}", v[1]); }
    fn consume(v: Vec<i32>) { /* `v` *dropped* at scope end */ }

foo(r1) attempts an indirect read of v1

error: cannot move out of `v1` because it is borrowed
    consume(v1);
            ^~
note: borrow of `v1` occurs here
    let r1 = &v1;
              ^~

Lifetimes and Lexical Scope

fn borrow_checking_may_seem_simple_minded() {

    let v1 = vec![1, 2, 3];      //        +
                                 //        |
    let r1 = &v1;                //  +    'v1
                                 //  |     |
    consume(v1);                 // 'r1   (moved)
    // (no call to read)         //  |
}                                //  +
    fn foo<'a>(v: &'a Vec<i32>) { println!("v[1]: {}", v[1]); }
    fn consume(v: Vec<i32>) { }
error: cannot move out of `v1` because it is borrowed
    consume(v1);
            ^~
note: borrow of `v1` occurs here
    let r1 = &v1;
              ^~

(artifact of lexical-scope based implementation)

Built on lexical scopes, but non-trivial

#[test]
fn copying_can_extend_a_borrows_lifetime_1() {
    fn foo<'a>(v: &'a Vec<i32>) { println!("v[1]: {}", v[1]); }
    let v1 = vec![1, 2, 3]; //         +
    let v2 = vec![4, 5, 6]; //         |    +
    let r2 = {              //         |    |
        let r1 = &v1;       //  +      |    |
        //       ~~~ <--- A //  |      |    |
        foo(r1);            // 'r1     |    |
        &v2                 //  |     'v1  'v2
    };                      //  +  +   |    |
    // (maybe mutate `v1`   //     |   |    |
    // here someday?)       //     |   |    |
                            //    'r2  |    |
    foo(r2);                //     |   |    |
}                           //     +   +    +

How long should the borrow &v1 last?

Built on lexical scopes, but non-trivial

#[test]
fn copying_can_extend_a_borrows_lifetime_2() {
    fn foo<'a>(v: &'a Vec<i32>) { println!("v[1]: {}", v[1]); }
    let v1 = vec![1, 2, 3]; //         +
    let v2 = vec![4, 5, 6]; //         |    +
    let r2 = {              //         |    |
        let r1 = &v1;       //  +      |    |
        //       ~~~ <--- A //  |      |    |
        foo(r1);            // 'r1     |    |
        r1  // <--------- B //  |     'v1  'v2
    };                      //  +  +   |    |
    // (maybe mutate `v1`   //     |   |    |
    // here someday?)       //     |   |    |
                            //    'r2  |    |
    foo(r2);                //     |   |    |
}                           //     +   +    +

How long should the borrow &v1 last now?

imm-borrows: can be copied freely

(super super useful to be able to share readable data!)

imm-borrows: can be copied freely

Implications:

  • must assume aliased (perhaps by another thread)
  • therefore not safe to mutate in general
#[test]
fn demo_cannot_mutate_imm_borrow() {
    let mut v1 = vec![1, 2, 3];
    let b = &v1;
    let (b1, b2, b3) = (b, b, b);
    try_modify(b);
    println!("v1: {:?}", v1);
}

fn try_modify(v: &Vec<i32>) {
    v.push(4);
}
error: cannot borrow immutable borrowed content `*v` as mutable
    v.push(4);
    ^

imm-borrows: can be copied freely

Implications:

  • must assume aliased (perhaps by another thread)
  • therefore not safe to mutate in general
#[test]
fn demo_cannot_mutate_imm_borrow() {
    let mut v1 = vec![1, 2, 3];
    let b = &v1;
    let (b1, b2, b3) = (b, b, b);
    try_modify(b);
    println!("v1: {:?}", v1);
}

fn try_modify(v: &Vec<i32>) {
    v.push(4);
}
WHAT
      A
         BUMMER!!!

"... I want my imperative algorthms! ..."

&mut borrows

#[test]
fn demo_can_mutate_mut_borrow() {
    let mut v1 = vec![1, 2, 3];
    modify(&mut v1);
    println!("v1: {:?}", v1);
}

fn modify(v: &mut Vec<i32>) {
    v.push(4);
}
v1: [1, 2, 3, 4]

What does &mut mean (crucial)

&mut is not about "being the only way to mutate"

  • It is about exclusive access

An operation requiring exclusive access should either:

  • take ownership, or,

  • take an &mut-reference

&mut is about exclusive access

"mut means 'mutate' ..." is a fiction

For many types, safe mutation does require exclusive access

vec.push(4);
// requires `vec: &mut Vec<_>`, for safe manipulation of backing store

"mut means 'mutate' ..." is a convenient fiction

(For related naming drama, do a search for: "mutpocalypse")

&mut safety enforcement

Data has at most one &mut borrow

fn take2<'a>(v1: &'a mut Vec<i32>, v2: &'a Vec<i32>) { }
#[test]
fn demo_cannot_mut_borrow_multiple_times() {
    let mut v1 = vec![1, 2, 3];
    let mut v2 = vec![1, 2, 3];
    take2(&mut v1, &mut v2); // <-- this is okay
    take2(&mut v1, &mut v1);
}
error: cannot borrow `v1` as mutable more than once at a time
    take2(&mut v1, &mut v1);
                        ^~
note: previous borrow of `v1` occurs here; the mutable borrow
      prevents subsequent moves, borrows, or modification of
      `v1` until the borrow ends
    take2(&mut v1, &mut v1);
               ^~

Cannot alias &mut-borrowed data

fn take2<'a>(v1: &'a mut Vec<i32>, v2: &'a Vec<i32>) { }
#[test]
fn demo_cannot_alias_mut_borrowed_data() {
    let mut v1 = vec![1, 2, 3];
    let mut v2 = vec![1, 2, 3];
    take2(&mut v1, &v2); // <-- this is okay
    take2(&mut v1, &v1);
}
error: cannot borrow `v1` as immutable because it is also borrowed
       as mutable
    take2(&mut v1, &v1);
                    ^~
note: previous borrow of `v1` occurs here; the mutable borrow 
      prevents subsequent moves, borrows, or modification of `v1`
      until the borrow ends
    take2(&mut v1, &v1);
               ^~

&mut T is non-copy

fn take2<'a>(v1: &'a mut Vec<i32>, v2: &'a Vec<i32>) { }
#[test]
fn demo_cannot_copy_mut_borrows() {
    let mut v1 = vec![1, 2, 3];
    let b = &mut v1;
    let c = b;
    take2(b, c);
}
error: use of moved value: `*b` [E0382]
    take2(b, c);
          ^
note: `b` moved here because it has type
      `&mut collections::vec::Vec<i32>`, which is moved by default
    let c = b;
        ^

(ensures exclusive access)

Exclusive Access versus Ownership

fn take_by_value(v: Vec<i32>) { let mut v = v; v.push(4);  }
fn take_mut_borrow(b: &mut Vec<i32>) { b.push(10); }
// seemingly similar in power
#[test]
fn demo_exclusive_access_versus_ownership() {
    let mut v1 = vec![1, 2, 3];
    let mut v2 = vec![7, 8, 9];
    take_by_value(v1);
    take_mut_borrow(&mut v2);
    println!("v1: {:?} v2: {:?}", v1, v2);
}
error: use of moved value: `v1` [E0382]
    println!("v1: {:?} v2: {:?}", v1, v2);
                                  ^~
note: `v1` moved here
    take_by_value(v1);
                  ^~

ownership ⇒ moves; power + responsibility for dropping

Exercises

Exercises 2

http://pnkfelix.github.io/cyot/tutorial/exercises/ex_part_2.html

Part 2: Programming in the Large

Everyone's installed (right?)

What is local development like?

  • rustc

  • cargo

  • modules and the filesystem

  • crates.io

rustc

[bash]$ cat /tmp/hello.rs

/tmp/hello.rs

fn main() { println!("hello world"); }
[bash]$ rustc /tmp/hello.rs -o /tmp/hello
[bash]$ /tmp/hello
hello world
[bash]$

Getting started with cargo

[bash]$ cargo new my-new-crate
[bash]$ cd my-new-crate
[bash]$ find * -type f
Cargo.toml
src/lib.rs
[bash]$ cat src/lib.rs

src/lib.rs

#[test]
fn it_works() {
}
[bash]$ cargo build
   Compiling my-new-crate v0.1.0 (file:///private/tmp/my-new-crate)
[bash]$ 
[bash]$ cargo test
   Compiling my-new-crate v0.1.0 (file:///private/tmp/my-new-crate)
     Running target/debug/my_new_crate-7ad82271427661a1

running 1 test
test it_works ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured

   Doc-tests my-new-crate

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured

[bash]$ 
[bash]$ cd ..
[bash]$ cargo new --bin my-rust-program
[bash]$ cd my-rust-program
[bash]$ find * -type f
Cargo.toml
src/main.rs
[bash]$ cat src/main.rs

src/main.rs

fn main() {
    println!("Hello, world!");
}
[bash]$ cargo run
   Compiling my-rust-program v0.1.0 (file:///private/tmp/my-rust-program)
     Running `target/debug/my-rust-program`
Hello, world!
[bash]$ 

crates versus modules

  • Crate: A unit of compilation

  • Module: A collection of items

  • Each crate is a tree of modules

mod tree hierarchy

src/lib.rs

mod a {
    mod b { pub type I = i32; }
    mod c {

        pub fn add3(x: super::b::I) -> super::b::I { x + 3 }
    }
}

(whoa, all those super::b::I paths are ugly)

can use any kind of item

src/lib.rs

mod a {
    mod b { pub type I = i32; }
    mod c {
        use a::b;
        pub fn add3(x: b::I) -> b::I { x + 3 }
    }
}

or even rename

src/lib.rs

mod a {
    mod b { pub type I = i32; }
    mod c {
        use a::b::I as J;
        pub fn add3(x: J) -> J { x + 3 }
    }
}

(consult your local style guidelines)

mod hierarchy and file system

Everything can be inline:

src/lib.rs

mod a {
    mod b { pub type I = i32; }
    mod c {
        use a::b::I;
        pub fn add3(x: I) -> I { x + 3 }
    }
}

mod hierarchy and file system

mod a {
    mod b { pub type I = i32; }
    mod c {
        use a::b::I;
        pub fn add3(x: I) -> I { x + 3 }
    }
}

Shorthand: 'mod name;' with subfiles at proper paths

src/lib.rs

mod a {
    mod b { pub type I = i32; }
    mod c;
}

src/a/c.rs

        use a::b::I;
        pub fn add3(x: I) -> I { x + 3 }

(Obviously do not indent your code this way.)

mod foo; versus use foo;

  • The syntax mod foo; is just sugar for

    mod foo {
      << insert contents of foo.rs here >>
    }
  • The mod-syntax creates definitions

  • The use-syntax imports bindings into namespace

  • Beginners often erroneosuly write mod foo; when they meant use foo;

pub (privacy)

items are private by default

(usually)

using pub items

mod d {
    pub type I = i32;
    pub fn add3(x: I) -> I { add3priv(x) }
    fn add3priv(x: I) -> I { x + 3 }
}
mod e {
    use super::d::add3;
    #[test] fn t() { assert_eq!(add3(1), 4); }
}

(this works)

privacy

mod d {
    pub type I = i32;
    pub fn add3(x: I) -> I { add3priv(x) }
    fn add3priv(x: I) -> I { x + 3 }
}
mod f {
    use super::d::add3priv;
    #[test] fn t() { assert_eq!(add3priv(1), 4); }
}
error: function `add3priv` is private
    use super::d::add3priv;
        ^~~~~~~~~~~~~~~~~~

crates.io

  • Cargo's main feature: dependency management

  • Before hacking something up, check crates.io first

  • Adding a third-party crate like quickcheck is as simple as adding this to the Cargo.toml

Cargo.toml

[dependencies]
quickcheck = "0.2.20"

Then add this:

lib.rs

extern crate quickcheck;

to your lib.rs, and you have access to quickcheck!

Part 3: Sequence Types and Iteration

Vectors and Arrays

Array [T; k] vs vector Vec<T>

#[test]
fn demo_vec_and_array() {
    // Heap-allocated vector
    let vec: Vec<i32> = vec![1000, 200, 32, 4];
    // Stack-allocated array
    let array: [i32; 4] = [2000, 400, 32, 4];








}

Iteration over ranges

Range constructor: start..limit

#[test]
fn demo_range() {

    let vec: Vec<i32> = vec![1000, 200, 32, 4];

    let array: [i32; 4] = [2000, 400, 32, 4];

    for i in 0..2 {
        //   ~~~~ range from 0 (inclusive) to 2 (exclusive)
        assert_eq!(vec[i] * 2, array[i]);
    }
    for i in 2..4 {
        assert_eq!(vec[i], array[i]);
    }
}

Vectors and Arrays and Slices

Shared slice of a sequence: [T] (cannot be stored on stack)

#[test]
fn demo_slice() {

    let vec: Vec<i32> = vec![1000, 200, 32, 4];

    let array: [i32; 4] = [2000, 400, 32, 4];



    let slice_1: &[i32] = &vec[2..4];
    let slice_2: &[i32] = &array[2..4];
    assert_eq!(slice_1, slice_2);

    assert_eq!(slice_1[0], slice_1[1] * 8);
}

for and iterators

#[test] fn demo_iters_1() {
    let zvecs = vec![vec![0,0], vec![0,0], vec![0,0], vec![0,0]];

    // Every `for` loop takes an iterator.

    // Some iterators are made by *consuming* input:
    let v = vec![1000, 1001, 1002, 1003];
    for i in vec![0, 1, 2, 3] {
        assert_eq!(v[i], 1000 + i);
    }

    // Some iterators are made by *borrowing* input:
    for elem in &zvecs {
        let elem: Vec<i32> = elem;     // <-- errors here
        assert_eq!(elem, vec![0,0]);   // <--- and here
    }
}

for and iterators

#[test] fn demo_iters_2() {
    let zvecs = vec![vec![0,0], vec![0,0], vec![0,0], vec![0,0]];

    // Every `for` loop takes an iterator.

    // Some iterators are made by *consuming* input:
    let v = vec![1000, 1001, 1002, 1003];
    for i in vec![0, 1, 2, 3] {
        assert_eq!(v[i], 1000 + i);
    }

    // Some iterators are made by *borrowing* input:
    for elem in &zvecs {
        let elem: &Vec<i32> = elem;    // <-- this is (one)
        assert_eq!(elem, &vec![0,0]); // <--- way to fix
    }
}

String and &str

  • String and Vec<T>: owned, growable

  • str and [T]: fixed-size, cannot be stored on stack

  • Both String and str are UTF-8 (a safety guarantee)

#[test]
fn demo_string_and_str() {
    let mut hw: String = String::new();
    hw.push_str("Hello");
    hw.push_str(" ");
    hw.push_str("World!");
    assert_eq!(hw, "Hello World!");

    let h: &str = &hw[0..5];
    let w: &str = &hw[6..11];

    assert_eq!(h, "Hello");
    assert_eq!(w, "World");
}

Iterator API

Every iterator inherits many high-level methods

#[test]
fn demo_iter_methods_1() {
    let v1: Vec<&str> = vec!["Hello", "to", "all", "da", "World!"];
    let v2: Vec<&str> = v1.iter()    // borrowing iterator for vec
        .filter(|w| { w.len() > 3 }) // del entries of length <= 3 
        .map(|p| -> &str { *p })     // deref each by one level
        .collect();                  // collect into target vec
    println!(" v1: {:?} \n v2: {:?}", v1, v2);
}

prints

 v1: ["Hello", "to", "all", "da", "World!"]
 v2: ["Hello", "World!"]

Iterator API

There is some cool type-based magic

#[test]
fn demo_iter_methods_2() {
    let v1: Vec<&str> = vec!["Hello", "to", "all", "da", "World!"];
    let s2: String    = v1.iter()    // borrowing iterator for vec
        .filter(|w| { w.len() > 3 }) // del entries of length <= 3 
        .map(|p| -> &str { *p })     // deref each by one level
        .collect();                  // collect into target string
    println!(" v1: {:?} \n s2: {:?}", v1, s2);
}

prints

 v1: ["Hello", "to", "all", "da", "World!"] 
 s2: "HelloWorld!"

Iterator API

All magic needs ingredients to work:

#[test]
fn demo_iter_methods_3() {
    let v1            = vec!["Hello", "to", "all", "da", "World!"];
    let x2            = v1.iter()    // borrowing iterator for vec
        .filter(|w| { w.len() > 3 }) // del entries of length <= 3 
        .map(|p| -> &str { *p })     // deref each by one level
        .collect();                  // collect into target ?????
    println!(" v1: {:?} \n x2: {:?}", v1, x2);
}

error

error: unable to infer enough type information about `_`; 
       type annotations or generic parameter binding required
    let x2 = v1.iter()
        ^~

Exercises

Exercises 3

http://pnkfelix.github.io/cyot/tutorial/exercises/ex_part_3.html

Part 4: Back to Language Fundamentals

structs

#[derive(Copy, Clone)]
struct Point { pub x: i32, pub y: i32 }

fn proj_x_axis(p: &Point) -> Point {
    Point { x: p.x, y: 0 }
}

fn nudge_left(p: &mut Point) { p.x -= 10; }

Or add a method:

impl Point {
    fn proj_x_axis(&self) -> Point {
        Point { x: self.x, y: 0 }
    }
}

tuple-structs, generics

#[derive(Copy, Clone)]
struct Angle(pub f64);   // construct with e.g. `Angle(90.0)`

struct Pair<X,Y>(X, Y);  // now `Pair(1,2)` or `Pair("a","b")` work

enums

enum Line {
    Segment { start: Point, end: Point },
    Vector(Point, Angle),
}

Pattern-matching:

fn start(l: &Line) -> Point {
    match *l {
        Line::Vector(s, _) => s,
        Line::Segment { start: s, .. } => s,
    }
}

Pattern-matching

fn start(l: &Line) -> Point {
    match *l {
        Line::Vector(s, _) => s,
        Line::Segment { start: s, .. } => s,
    }
}

Richer than C-style switch:

    match *l { // (not doing anything meaningful)
        Line::Vector(Point { x: 0, y }, _) => y,
        Line::Segment { start: Point { x, y: 0 }, .. } => x,
        _ => false,
    }

See also Mixing matching, mutation, and moves blog post.

Option and Result

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

enum Result<T, E> { Ok(T), Err(E) }

// impl<T, E> Result<T, E> { pub fn ok(self) -> Option<T> { ... } }
use std::num::ParseIntError;
fn read_u32(s: &str) -> Result<u32, ParseIntError> {
    s.parse()
}

#[test]
fn demo() {
    assert_eq!(read_u32("4"), Ok(4));
    assert!(read_u32("4_no_no").is_err());

    assert_eq!(read_u32("4").ok(), Some(4));
}

Lifetime Bindings 1

We saw this kind of thing before:

#[test]
fn explicit_lifetime_binding_1() {
    fn print<'a>(ints: &'a Vec<i32>) {
        println!("v_1: {}", ints[1]);
    }
    let v1 = vec![1, 2, 3];
    print(&v1)
}

Lifetime Bindings 2

You can bind distinct lifetimes:

#[test]
fn explicit_lifetime_binding_2() {
    fn print<'a, 'b>(ptrs: &'a Vec<&'b i32>) {
        println!("v_1: {}", ptrs[1]);

    }
    let one = 1;
    let two = 2;
    let three = 3;
    let four = 4;
    let v1 = vec![&one, &two, &three];
    print(&v1)
}

Lifetime Bindings 3

Encode constraints by reusing same lifetime:

#[test]
fn explicit_lifetime_binding_3() {
    fn print<'a, 'b>(ptrs: &'a mut Vec<&'b i32>, ptr: &'b i32) {
        println!("v_1: {}", ptrs[1]);
        ptrs.push(ptr);
    }
    let one = 1;
    let two = 2;
    let three = 3;
    let four = 4;
    let mut v1 = vec![&one, &two, &three];
    print(&mut v1, &four);
}

Lifetime Bindings 4

Encode constraints by reusing same lifetime:

#[test]
fn explicit_lifetime_binding_4() {
    fn print<'a, 'b>(ptrs: &'a mut Vec<&'b i32>, ptr: &'b i32) {
        println!("v_1: {}", ptrs[1]);//~~~            ~~~
        ptrs.push(ptr);            //   |              |
    }                              // this must match that,
    let one = 1;                   // otherwise push is bogus
    let two = 2;
    let three = 3;
    let four = 4;
    let mut v1 = vec![&one, &two, &three];
    print(&mut v1, &four);
}

Lifetime Bindings 5

Compiler catches missing necessary constraints:

#[test]
fn explicit_lifetime_binding_5() {
    fn print<'a, 'b, 'c>(ptrs: &'a mut Vec<&'b i32>, ptr: &'c i32) {
        println!("v_1: {}", ptrs[1]);  //  ~~~            ~~~
        ptrs.push(ptr);                //   |              |
    }                                  // this must match that,
    let one = 1;                       // otherwise push is bogus
}
error: cannot infer an appropriate lifetime for automatic coercion
       due to conflicting requirements
        ptrs.push(ptr);
                  ^~~
help: consider using an explicit lifetime parameter as shown:
    fn print<'a, 'b>(ptrs: &'a mut Vec<&'b i32>, ptr: &'b i32)

Borrowed return values 1

fn first_n_last<'a>(ints: &'a Vec<i32>) -> (&'a i32, &'a i32) {
    //                                      ~~~~~~~  ~~~~~~~
    (&ints[0], &ints[ints.len() - 1])
}
#[test]
fn demo_borrowed_return_values() {
    let v = vec![1, 2, 3, 4];
    let (first, fourth) = first_n_last(&v);
    assert_eq!(*first, 1);
    assert_eq!(*fourth, 4);
}

(compiler ensures borrow &v lasts long enough to satisfy reads of first and fourth)

Borrowed return values 2

fn first_n_last<'a>(ints: Vec<i32>) -> (&'a i32, &'a i32) {
    //                    ~~~~~~~~ (hint)
    (&ints[0], &ints[ints.len() - 1])
}

Why doesn't this work?

error: `ints` does not live long enough
    (&ints[0], &ints[ints.len() - 1])
      ^~~~
note: reference must be valid for the lifetime 'a ...
note: ...but borrowed value is only valid for the scope of
note:    parameters for function

caller chooses 'a; fn body must work for any such choice

(Parameters dropped at scope end; won't live long enough)

Lifetime Elision

All the 'a, 'b, ... are ugly

Lifetime Elision 1

#[test]
fn lifetime_elision_1() {
    fn print1<'a>(ints: &'a Vec<i32>) {
        println!("v_1: {}", ints[1]);
    }
    fn print2<'a, 'b>(ptrs: &'a Vec<&'b i32>) {
        println!("v_1: {}", ptrs[1]);

    }
    fn print3<'a, 'b>(ptrs: &'a mut Vec<&'b i32>, ptr: &'b i32) {
        println!("v_1: {}", ptrs[1]);
        ptrs.push(ptr);
    }
}

Lifetime Elision 2

#[test]
fn lifetime_elision_2() {
    fn print1    (ints: &   Vec<i32>) {
        println!("v_1: {}", ints[1]);
    }
    fn print2        (ptrs: &   Vec<&   i32>) {
        println!("v_1: {}", ptrs[1]);

    }
    fn print3<    'b>(ptrs: &   mut Vec<&'b i32>, ptr: &'b i32) {
        println!("v_1: {}", ptrs[1]);
        ptrs.push(ptr);
    }
}

Lifetime Elision 3

#[test]
fn lifetime_elision_3() {
    fn print1(ints: &Vec<i32>) {
        println!("v_1: {}", ints[1]);
    }
    fn print2(ptrs: &Vec<&i32>) {
        println!("v_1: {}", ptrs[1]);

    }
    fn print3<'b>(ptrs: &mut Vec<&'b i32>, ptr: &'b i32) {
        println!("v_1: {}", ptrs[1]);
        ptrs.push(ptr);
    }
}

Generic items

Generic items 1

#[test]
fn generic_items_1() {
    fn push_twice<'b>(ptrs: &mut Vec<&'b i32>, ptr: &'b i32) {
        ptrs.push(ptr);
        ptrs.push(ptr);
    }
    let (one, two, three, four) = (1, 2, 3, 4);
    let mut v = vec![&one, &two, &three];
    push_twice(&mut v, &four);
}

This obviously generalizes beyond i32!

Generic items 2

#[test]
fn generic_items_2() {
    fn push_twice<'b, T>(ptrs: &mut Vec<&'b T>, ptr: &'b T) {
        ptrs.push(ptr);
        ptrs.push(ptr);
    }
    let (one, two, three, four) = (1, 2, 3, 4);
    let mut v = vec![&one, &two, &three];
    push_twice(&mut v, &four);
}

This is going so smoothly; lets try printing v_1 again!

Generic items 3

#[test]
fn generic_items_3() {
    fn push_twice<'b, T>(ptrs: &mut Vec<&'b T>, ptr: &'b T) {
        println!("v_1: {}", ptrs[1]);
        ptrs.push(ptr);
        ptrs.push(ptr);
    }
    let (one, two, three, four) = (1, 2, 3, 4);
    let mut v = vec![&one, &two, &three];
    push_twice(&mut v, &four);
}
error: trait `core::fmt::Display` not implemented for the type `T`
        println!("v_1: {}", ptrs[1]);
                            ^~~~~~~

(Reminder: Rust is not C++)

Trait-bounded polymorphism

trait Dimensioned {
    fn height(&self) -> u32;
    fn width(&self) -> u32;
}

fn stacked_height<S>(v: &[S]) -> u32 where S: Dimensioned {
    let mut accum = 0;
    for s in v { accum += s.height() }
    accum
}

Trait Impls

struct Rect { w: u32, h: u32 }
struct Circle { r: u32 }

impl Dimensioned for Rect {
    fn height(&self) -> u32 { self.h }
    fn width(&self) -> u32 { self.w }
}

impl Dimensioned for Circle {
    fn height(&self) -> u32 { self.r * 2 }
    fn width(&self) -> u32 { self.r * 2 }
}

Traits in Action

impl Rect {
    fn square(l: u32) -> Rect { Rect { w: l, h: l } }
}
impl Circle {
    fn with_radius(r: u32) -> Circle { Circle { r: r } }
}

#[test]
fn trait_bounded_polymorphism() {
    let squares = [ Rect::square(1), Rect::square(2) ];
    let circles = [ Circle::with_radius(1), Circle::with_radius(2)];
    assert_eq!(stacked_height(&squares), 3);
    assert_eq!(stacked_height(&circles), 6);
}

Generics do not suffice

#[test]
fn parametric_fail() {
    let shapes = [Rect::square(1), Circle::with_radius(2)];
    assert_eq!(stacked_height(&shapes), 5);
}
error: mismatched types:
 expected `Rect`,
    found `Circle`
    let shapes = [Rect::square(1), Circle::with_radius(2)];
                                   ^~~~~~~~~~~~~~~~~~~~~~

Uniformity of T in Vec<T> is why

struct Rect { w: u32, h: u32 }
struct Circle { r: u32 }

fn parametric_fail() {
    let shapes = [Rect::square(1), Circle::with_radius(2)];
    //  ~~~~~~    ~~~~~~~~~~~~~~~  ~~~~~~~~~~~~~~~~~~~~~~
    //    |              |                    |
    //    |       This is 8 bytes     This is 4-bytes
    //    |
    //  There's no uniform array
    //  type to hold both in-line.
}

This is a job for ...

Object-Oriented Programming!

Traits as Objects 1

fn stacked_obj_refs(v: &[&Dimensioned]) -> u32 {
    let mut accum = 0;
    for s in v { accum += s.height() }
    accum
}

#[test]
fn demo_objs_1() {
    let r = Rect::square(1);
    let c = Circle::with_radius(2);
    let shapes: [&Dimensioned; 2] = [&r, &c];
    assert_eq!(stacked_obj_refs(&shapes), 5);
}

Traits as Objects 2

fn stacked_obj_boxes(v: &[Box<Dimensioned>]) -> u32 {
    let mut accum = 0;
    for s in v { accum += s.height() }
    accum
}

#[test]
fn demo_objs_2() {
    let shapes: [Box<Dimensioned>; 2] =
        [Box::new(Rect::square(1)), Box::new(Circle::with_radius(2))];
    assert_eq!(stacked_obj_boxes(&shapes), 5);
}

OOP is nice; how about Functional Programming?

Closures 1

  • Can pass functions around as first class entities

  • Functions can close over externally defined state

Reminder from Javascript:

closures.js

function add3(x) { return x + 3; }

// take function as parameter:
function do_twice(f, y) { return f(f(y)); }

// return function that references outer parameter `z`
function make_adder(z) {
    return function(w) { return w + z; };
}

var add4 = make_adder(4);
var ten = do_twice(add4, 2);

Closures 2

  • In (classic) Javascript, closure syntax is:

    function (args, ...) { body; ... }

    where body can refer to things from outside.

  • In Rust, the analogous closure expression syntax is:

    |args, ...| { body; ... }

    with a few extra details:

  • opt. move (forces capture-by-move)

  • opt. arg. and return types (inferred when omitted)

Closures 3

#[test]
fn demo_closure() {
    fn add3(x: i32) -> i32 { x + 3 } // <- fn, *not* a closure
    fn do_twice1<F:Fn(i32) -> i32>(f: F, x: i32) -> i32 { f(f(x)) }
    //             ~~~~~~~~~~~~~~ closure type
    fn do_twice2(f: &Fn(i32) -> i32, x: i32) -> i32 { f(f(x)) }

    fn make_adder(y: i32) -> Box<Fn(i32) -> i32> {
        Box::new(move |x| { x + y })
            //   ~~~~~~~~~~~~~~~~~~ closure expression
    }

    let add4 = make_adder(4);
    let six = do_twice1(&add3, 0); let ten = do_twice1(&*add4, 2);
    assert_eq!((six, ten), (6, 10));
    let six = do_twice2(&add3, 0); let ten = do_twice2(&*add4, 2);
    assert_eq!((six, ten), (6, 10));
}

Part 5: Systems Development

Concurrency

Rust's killer feature:

Data-race freedom

built atop same foundation as memory safety

Here's what one concurrency API looks like

thread::spawn

pub fn main() {
    use std::thread;
    let al = "long lost pal";
    thread::spawn(move || {

        println!("i can be your {}", al);
    });

    println!("why am i soft in the middle");
    // Note: might exit before spawned thread gets chance to print
}

channels for message passing

#[test] fn demo_channel() {
    fn fib(x: i64) -> (i64, i64) { // returns `(x, fib(x))`
        if x <= 1 { (x,1) } else { (x, fib(x-1).1 + fib(x-2).1) }
    }
    use std::thread;
    use std::sync::mpsc::channel;
    let (tx, rx) = channel(); // tx: "transmit", rx: "receive"
    let al = "al";
    thread::spawn(move || {
        tx.send(fib(10));
        println!("you can call me {}", al);
    });
    let f_15 = fib(15).1;
    println!("why am i short of attention");
    let f_10 = rx.recv().unwrap().1; // (this blocks to await data)
    assert_eq!((f_10, f_15), (89, 987));
}

channels are abstraction, data-race free

No data races: What about our precious mutation?

No data races 1: "direct" assign

#[test] fn demo_catch_direct() {
    fn fib(x: i64) -> (i64, i64) { // returns `(x, fib(x))`
        if x <= 1 { (x,1) } else { (x, fib(x-1).1 + fib(x-2).1) }
    }
    use std::thread;
    let al = "al";
    let mut f_10_recv = (0, 0);

    thread::spawn(move || {
        f_10_recv = fib(10);
        println!("you can call me {}", al);
    });
    let f_15 = fib(15).1;
    while f_10_recv.0 == 0 { }  // <-- many alarm bells
    let f_10 = f_10_recv.1;
    println!("why am i short of attention");
    assert_eq!((f_10, f_15), (89, 987));
}

compiles; does not work (no actual communication; implicit copying)

No data races 2: mut-ref

#[test] fn demo_catch_mutref() {
    fn fib(x: i64) -> (i64, i64) { // returns `(x, fib(x))`
        if x <= 1 { (x,1) } else { (x, fib(x-1).1 + fib(x-2).1) }
    }
    use std::thread;
    let al = "al";
    let mut f_10_recv = (0, 0);
    let ptr_recv = &mut f_10_recv; // <-- Okay, say what we meant
    thread::spawn(move || {
        *ptr_recv = fib(10);
        println!("you can call me {}", al);
    });
    let f_15 = fib(15).1;
    while f_10_recv.0 == 0 { }  // <-- many alarm bells
    let f_10 = f_10_recv.1;
    println!("why am i short of attention");
    assert_eq!((f_10, f_15), (89, 987));
}

does not compile: spawn can't share ref to stack-local

Here's a totally different concurrency API

thread::scoped

fn seq_max(partial_data: &[u8]) -> u8 {
    *partial_data.iter().max().unwrap()
}

fn par_max(data: &[u8]) -> u8 {
    if data.len() <= 4 { return seq_max(data); }
    let len_4 = data.len() / 4; // DATA = [A..B..C..D..]
    let (q1, rest) = data.split_at(len_4); // (A.. \ B..C..D..)
    let (q2, rest) = rest.split_at(len_4); //  (B.. \ C..D..)
    let (q3, q4)   = rest.split_at(len_4); //   (C.. \ D..)
    let t1 = ::std::thread::scoped(|| seq_max(q1)); // fork A..
    let t2 = ::std::thread::scoped(|| seq_max(q2)); // fork B..
    let t3 = ::std::thread::scoped(|| seq_max(q3)); // fork C..
    let v4 = seq_max(q4); //                        compute D..
    let (v1, v2, v3) = (t1.join(), t2.join(), t3.join()); // join!
    return seq_max(&[v1, v2, v3, v4]);
}

thread::scoped shows a new trick

  • thread::spawn disallowed passing refs to stack-local data

  • Allowing that is the whole point of thread::scoped

    • (caveat: thread::scoped API is unstable, and undergoing revision due to subtle soundness issue)

Benchmarking par_max 1

extern crate test; use std::iter;
const LIL: usize = 20 * 1024;
const BIG: usize = LIL * 1024;

fn make_data(count: usize) -> Vec<u8> {
    let mut data: Vec<u8> = iter::repeat(10).take(count).collect();
    data.push(200); data.push(3); return data;
}

#[bench] fn bench_big_seq(b: &mut test::Bencher) {
    let data = make_data(BIG);
    b.iter(|| assert_eq!(seq_max(&data), 200));
}
#[bench] fn bench_big_par(b: &mut test::Bencher) {
    let data = make_data(BIG);
    b.iter(|| assert_eq!(par_max(&data), 200));
}
bench_big_par ... bench:   3,763,711 ns/iter (+/- 1,140,321)
bench_big_seq ... bench:  21,633,799 ns/iter (+/- 2,522,262)

Benchmarking par_max 2

const LIL: usize = 20 * 1024;
const BIG: usize = LIL * 1024;
bench_big_par ... bench:   3,763,711 ns/iter (+/- 1,140,321)
bench_big_seq ... bench:  21,633,799 ns/iter (+/- 2,522,262)
#[bench] fn bench_lil_seq(b: &mut test::Bencher) {
    let data = make_data(LIL);
    b.iter(|| assert_eq!(seq_max(&data), 200));
}
#[bench] fn bench_lil_par(b: &mut test::Bencher) {
    let data = make_data(LIL);
    b.iter(|| assert_eq!(par_max(&data), 200));
}
bench_lil_par ... bench:      59,274 ns/iter (+/- 7,756)
bench_lil_seq ... bench:      15,432 ns/iter (+/- 1,961)

(fn par_max could tune threshold for seq. path)

What was that about preventing data races?

Send, Sync

  • If T: Send, then passing (e.g. moving) a T to another thread is safe.

  • If T: Sync, then copying a &T to another thread is safe.

  • (For Rust, "safe" includes "no data races exposed.")

Last few things

IO in Rust

Many input/output (IO) routines can encounter errors

1.0 philosophy: Result<T, Error>; callers handle

impl File { fn open<P:AsRef<Path>>(path: P) -> io::Result<File> }
impl<B: BufRead> Iterator for Lines<B> {       ~~~~~~~~~~
    type Item = io::Result<String>
}               ~~~~~~~~~~
#[test]
fn demo_io() {
    use std::fs::File;
    use std::io::{self, BufReader, BufRead};
    fn do_io() -> Result<(), io::Error> {
        let f = try!(File::open("/etc/ssh_config"));
        let buffered = BufReader::new(f);
        for l in buffered.lines().take(10) {
            println!("line: {}", try!(l));
        }
        Ok(()) // (need to remember the result!)
    }
    do_io().unwrap()
}

unsafe code

Bypass Rust's safety checking via unsafe (and take responsibility for safety)

#[should_panic]
#[test]
fn demo_out_of_bounds_access() {
    let cap = {
        let v0 = Vec::from("Goodbye World!");
        v0.capacity()
    }; // <--- `v0` backing store is free'd here
    let mut new_v: Vec<u8> = Vec::with_capacity(cap); // "uninitialized"
    unsafe { new_v.set_len(cap); }                    // UH OH
    println!("v[0..4]: {:?} b'Good': {:?}", &new_v[0..4], b"Good");
    panic!("unsafe demo");
}

On my machine, prints:

v[0..4]: [71, 111, 111, 100] b'Good': [71, 111, 111, 100]
("Yay," we can still have security bugs!)

native pointers

Type classification, for any type T

// Safe References
&T
&mut T
// Unsafe pointers
*mut T
*const T
#[test]
fn demo_unsafe_pointer() {
    let x = [3, 4, 5];
    let p = &x[0] as *const i32;
    unsafe { println!("p[0]: {} p[2]: {}", *p, *p.offset(2)); }
}

prints:

p[0]: 3 p[2]: 5

FFI

use libc::c_int as int; use libc::c_void as void; use libc::size_t;
use std::mem::size_of;
extern "C" {
    fn qsort(base: *mut void, nel: size_t, width: size_t,
             compar: *const extern fn (*const void,
                                       *const void) -> int);
}
extern "C" fn compar_i32(x: *const void, y: *const void) -> int {
    unsafe { *(x as *const i32) - *(y as *const i32) }
}
#[test]
fn demo_ffi() {
    let mut data: [i32; 9] = [9, 8, 1, 2, 7, 6, 3, 4, 5];
    unsafe { let ptr = &mut data[0] as *mut i32;
        qsort(ptr as *mut void, 9, 4, compar_i32 as *const _);
    }
    assert_eq!(data, [1, 2, 3, 4, 5, 6, 7, 8, 9]);
}

Conclusion

Rust

  • Access to (many!) safe concurrency primitives

  • Access arbitrary native services

  • Ownership and Borrowing are deep parts of language

  • rustc catches bugs!

  • HACK WITHOUT FEAR!

Thanks everyone for attending

Lets thank the helpers from the Research team as well

Go out and hack some Rust!