rust

fearless systems programming

Hey, you! If you have yet to install Rust:

  1. Log onto the wireless, password "doubletreepgh"
  2. Go to www.rust-lang.org and click "Install"
  3. Run the installer (Windows & OS X) or decompress and run `./install.sh` (Linux)
  4. Run `rustc --version` to ensure that installation was successful

Ben Striegel

@bstrie

rust

fearless systems programming for the modern developer

"A systems language

Pursuing the trifecta

Safe, concurrent, fast."

- Lindsay Kuper

the rust haiku

who uses rust?

  • Systems programmers seeking greater safety and reliability
  • Application programmers seeking to speed up key parts of their code
  • High-level programmers looking to learn their first low-level language
  • Companies seeking a maintainable foundation for their stack
  • Startups looking to punch above their weight with a combination of efficiency and productivity

fast

"C++ implementations obey the zero-overhead principle: What you don’t use, you don’t pay for.

And further: What you do use, you couldn’t hand-code any better."

- bjarne stroustrup

"ZERO-COST ABSTRACTIONS"

// C
int sum = 0;
for (int i=0; i<x; i++) {
    sum += i;
}
# Python
sum = 0
for i in range(0, x):
    sum += i
# `sum()` ist verboten
// Rust
let mut sum = 0;
for i in 0..x {
    sum += i;
}

# Ruby
(0...x).inject(0) {|sum, i| sum + i}

# Python
reduce(lambda sum, i: sum + i,
    range(0, x), 0)

// Rust
(0..x).fold(0, |sum, i| sum + i)
; Assembly
lea	eax, [rdi - 1]
lea	ecx, [rdi - 2]
imul	rcx, rax
shr	rcx
lea	eax, [rcx + rdi - 1]
((x - 1) * (x - 2) >> 1) + x - 1
((x1)(x2)>>1)+x1((x - 1) * (x - 2) >> 1) + x - 1
// Rust
(0..x).fold(0, |sum, i| sum + i)

Thanks, LLVM

:)

safe

// C, runtime memory error
int* foo() {
    int x = 4;
    return &x;
}
// Go, safe thanks to GC
func foo() *int {
    x := 4
    return &x
}
// Rust, does not compile
fn foo() -> &i32 {
    let x = 4;
    return &x;
}
// Rust, does not compile
fn foo() -> &i32 {
    let x = 4;
    return &x;
}
error[E0106]: missing lifetime specifier
 --> foo.rs:1:13
  |
1 | fn foo() -> &i32 {
  |             ^ expected lifetime parameter
  |
  = help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from
// C++, invalidated iterator
for(auto i: v) {
    if (i > 0) {
        v.push_back(i * -1);
    }
}
// Java, runtime exception
for (Integer i: v) {
    if (i > 0) {
        v.add(i * -1);
    }
}
// Rust, does not compile
for i in v {
    if i > 0 {
        v.push(i * -1);
    }
}
error[E0382]: use of moved value: `v`
 --> invalidator.rs:3:9
  |
1 | for i in v {
  |          - value moved here
2 |     if i > 0 {
3 |         v.push(i * -1);
  |         ^ value used here after move

con-

current

// Go, with data race
x := 0
for i := 0; i < 10; i++ {
    go func() { x += 1 }()
}
// Go, without data race
mutex := &sync.Mutex{}
x := 0
for i := 0; i < 10; i++ {
    go func() {
        mutex.Lock()
        x += 1
        mutex.Unlock()
    }()
}
// Rust, does not compile
let mut x = 0;
for i in 0..10 {
    spawn(|| x += 1);
}
error[E0373]: closure may outlive the current function, but it borrows `x`, which is owned by the current function
 --> race.rs:3:11
  |
3 |     spawn(|| x += 1);
  |           ^^ - `x` is borrowed here
  |           |
  |           may outlive borrowed value `x`
// Rust
let x = Arc::new(Mutex::new(0));
for i in 0..10 {
    let y = x.clone();
    spawn(move || {
        let mut z = y.lock().unwrap();
        *z += 1;
    });
}

Things to note:

  • Cloning an `Arc` allows ownership among multiple threads
  • The `move` keyword on a closure captures by-value, similar to C++'s `[=]` capture clause
  • Mutexes in Rust don't just guard a section of source code, they actually guard access to pieces of data

basics

hello, world!

fn main() {
    println!("Hello!");
}
// C, Javascript
while (a) b;

for (a; c; d;) e;

for (;;) a;

if (a)
    b;
else if (c) {
    d;
    e;
} else
    f;
// Rust
while a { b; }

for a in b { c; }

loop { a; }

if a {
    b;
} else if c {
    d;
    e;
} else {
    f;
}

primitives

Signed fixed-width integers:

Unsigned fixed-width integers:

Platform-sized integers:

Floating point:

UTF-32 Unicode scalar values:

Booleans:

i8, i16, i32, i64
u8, u16, u32, u64
isize, usize
f32, f64
char
bool

collections

Fixed-size array of N elements:

Growable, heap-allocated buffer:

Growable, heap-allocated buffer of UTF-8:

Tuple containing several different types:

[T; N]
Vec<T>
String
(T, U, V)

See the `collections` module in the standard library for hashmaps, sets, B-trees, and more.

pointers

Shared reference:

Mutable reference:

Shared slice (pair of pointer and length):

Mutable slice:

String slice:

Heap-allocated smart pointer:

Reference-counted smart pointer:

Concurrent reference-counted smart pointer:

&T
&mut T
&[T]
&mut [T]
&str
Box<T>
Rc<T>
Arc<T>

literals

let a = 1;  // integer
let b = 2.0;  // float
let c = "three";  // static string slice
let d = '4';  // char
let e = [5, 6, 7];  // fixed-size array
let f = (8.0, "nine", 10);  // tuple
let g = vec![11, 12, 13];  // vector

cargo

Create new library project:

Create new application project:

Build and run in debug mode:

Build and run in release mode:

Build without running:

Run your test suite:

Update dependencies:

View command-line options:

cargo new foo
cargo new foo --bin
cargo run
cargo run --release
cargo build
cargo test
cargo update
cargo help

crates.io

  • Central package repository for Rust, similar to CPAN, PyPI, and RubyGems.org
  • Has grown to 6,600 packages since launching in late 2014
  • Serves as a comprehensive public regression test suite for the Rust compiler
  • Once uploaded, packages are immutable and irrevokable, to ensure repeatability

ANY

QUESTIONS?

exercise #1

Get the code at github.com/jimblandy/exercises

If you have git:

git clone https://github.com/jimblandy/exercises.git

If you don't have git, download and unzip:

https://github.com/jimblandy/exercises/archive/master.zip

Navigate into the exercises/ex1 directory and run `cargo test`

Observe the test failures

Edit the `is_prime` function in src/lib.rs until all tests pass

OPTIONAL: set up Rust support for your favorite code editor by perusing www.rust-lang.org/ides

the cool stuff

ownership

1. All variables are owned by their enclosing scope

2. When a scope ends, everything it owns is deallocated

3. When one variable is assigned to another variable, the original variable becomes inaccessible, because a piece of memory must have only one owner at a time

4. Passing a variable to a function will move ownership of that data into that function's scope, and make the original variable inaccessible

5. Returning a value from a function or scope will move ownership of that data into the enclosing scope

ownership

1. All variables are owned by their enclosing scope

fn foo() {  // this scope owns x and z
    let x = Foo;

    if true {  // this scope owns y
        let y = Bar;
    }

    let z = Qux;
}

ownership

2. When a scope ends, everything it owns is deallocated

fn foo() {
    let x = Foo;
    
    if true {
        let y = Bar;
    }  // y is deallocated
    
    let z = Qux;
}  // x and z are deallocated

ownership

3. When one variable is assigned to another variable, the original variable becomes inaccessible, because a piece of memory must have only one owner at a time

let x = Foo;
let y = x;
// COMPILATION ERROR BELOW
do_anything_with(x); 
error[E0382]: use of moved value: `x`
 --> three.rs:3:18
2 | let y = x;
  |     - value moved here
3 | do_anything_with(x);
  |                  ^ value used here after move

ownership

4. Passing a variable to a function will move ownership of that data into that function's scope, and make the original variable inaccessible

let x = Foo;
now_ur_mine(x);
// COMPILATION ERROR BELOW
let y = x;
error[E0382]: use of moved value: `x`
 --> four.rs:3:5
2 | now_ur_mine(x);
  |             - value moved here
3 | let y = x;
  |     ^ value used here after move

ownership

5. Returning a value from a function or scope will move ownership of that data into the enclosing scope

fn bar() -> Foo {
    let x = Foo;
    return x;
}

let y = bar();
// Putting it all together...

fn pass_it_through(y: Foo) -> Foo {
    return y;
}

fn end_of_the_line(z: Foo) {
    // memory_deallocated_here
}

let x = Foo;  // memory allocated here
let y = x;
let z = pass_it_through(y);
end_of_the_line(z);

ownership

There is one and only one way to alter the behavior of ownership: by making a type copyable

Once a type is copyable, moves will no longer render the original variable inaccessible

This is the complete extent of the user's ability to hijack the ownership system itself, i.e. there are no user-defined copy constructors as there are in C++

This mechanism exists because there's really no benefit to treating small, boring data types like booleans or integers as uniquely-owned pieces of data

traits

// Traits as interfaces

trait DoesSomething {
    fn do_something(self);
}

impl DoesSomething for i32 {
    fn do_something(self) {
        println!("Extending basic types!");
    }
}

6.do_something();

generics

fn not_generic(x: i32) -> i32 {
    return x;
}

fn totes_generic<T>(x: T) -> T {
    return x;
}
// Traits as bounds on generics

trait DoesSomething {
    fn do_something(self);
}

fn kinda_useless_generic<T>(x: T) {
    // we basically can't do anything with x,
    // because we don't know what it allows!
}

fn useful_generic<T: DoSomething>(x: T) {
    x.do_something();
}
impl DoesSomething for i32 {
    fn do_something(self) { println!("OMG!"); }
}

impl DoesSomething for bool {
    fn do_something(self) { println!("ZOMG!!"); }
}

fn kinda_useful_generic<T: DoSomething>(x: T) {
    x.do_something();
}

kinda_useful_generic(9);  // OMG!
kinda_useful_generic(true);  // ZOMG!!

traits in the standard lib

  • Traits for operator overloading, such as `Eq` for overloading the `==` operator, and `Add` for `+`
  • `Hash`, to denote types that can be keys in hash tables
  • `Copy`, the magic trait that makes types copyable, and `Clone`, to provide explicit copy constructors
  • `Debug`, for rudimentary automatically-generated string formatting for println-debugging

ANY

QUESTIONS?

exercise #2

custom data types

structs

// A typical struct
struct Foo {
    w: i32,
    x: usize
}

// A generic struct
struct Bar<T, U> {
    y: T,
    z: U
}

structs

struct Foo<T> {
    my_anything: T,
    my_num: i32
}

let bar = Foo { my_anything: "yo", my_num: 2 };
println!("{}", bar.my_anything);

fn spam() -> i32 { return 5; }

let qux = Foo { my_num: 3, my_anything: spam };
println!("{}", (qux.my_anything)());  // 5

enums

use MagicSpell::{Fireball, BalefulPolymorph};

enum MagicSpell {
    Fireball,
    BalefulPolymorph
}

fn make_spell_sound(spell: MagicSpell) {
    match spell {
        Fireball => println!("BOOM!"),
        BalefulPolymorph => println!("MOO!")
    }
}

enums

use IntList::{Node, Empty};

enum IntList {
    Node(i32, Box<IntList>),
    Empty
}

let x = Node(2,
    Box::new(Node(4,
    Box::new(Empty))));

enums

use Option::{Some, None};

// Redundant with the stdlib
enum Option<T> {
    Some(T),
    None
}

fn foo<T>(x: Option<T>) {
    match x {
        Some(y) => println!("Something!"),
        None => println!("Nothing :((("),
    }
}

enums

#[derive(Debug)]
enum MagicSpell {
    Fireball,
    BalefulPolymorph
}

let spell = MagicSpell::Fireball;

println!("{:?}", spell);

ANY

QUESTIONS?

exercise #3

borrowing

fn main(x: Foo) -> Foo {
    // man, really wish I
    // didn't have to return x...
}

fn main(x: &Foo) {
    // sheer ecstasy!
}

borrowing

Literally the hardest thing about learning Rust as a newcomer! The dreaded Borrow Checker!! :O

SECRET PRO TIP: The borrow checker only kicks in if you're using references, or are using something that might eventually hold references. If you don't want to deal with borrow check errors, then you can just clone and box everything until you feel comfortable.

DOUBLE SECRET PRO TIP: Download the nightly compiler and pass the experimental flag for new borrow check error message!

TRIPLE SECRET PRO TIP: Just ask on IRC! They're all seasoned borrow check veterans.

the standard library

advanced concurrency

  • Channels: multi-producer single-consumer FIFO queues. doc.rust-lang.org/std/sync/mpsc/index.html
  • Arc: Atomically-reference counted smart pointer for safely sharing data amongst threads.                 doc.rust-lang.org/std/sync/struct.Arc.html
  • Crossbeam: Lock-free datastructures without GC via epoch-based memory management. Also allows sharing stack frames among multiple threads. crates.io/crates/crossbeam
  • Rayon: Drop-in work-stealing parallel iterators. crates.io/crates/rayon

advanced tooling

  • play.rust-lang.org, an online Rust compiler, pastebin, and assembly inspector
  • rustdoc, built-in documentation generator via source-level doc comments
  • rustup.rs, a Rust toolchain manager for easy cross compilation and compiler version wrangling.
  • clippy, a comprehensive set of heuristic-based static analysis and code style suggestions for Rust

bonus exercise

getting involved

  • Github: github.com/rust-lang
  • Forums: users.rust-lang.org
  • IRC: #rust on irc.mozilla.org
  • Reddit: reddit.com/r/rust

Thanks for coming!

now, go forth and rust!

Get these slides at slides.com/bstrie/rust-rbr

Rust: Fearless Systems Programming [Rust Belt Rust 2016]

By bstrie

Rust: Fearless Systems Programming [Rust Belt Rust 2016]

  • 2,675