Rust @ Functional Vilnius

@nercury

Rust?

Safe

Efficient

Concurrent

SERVO

+

(think C++)

(think taaaabs)

(no memory mis-use)

Rust's path?

Control vs. Safety

CONTROL

SAFETY

C

C++

Go

Java

C#

Python

PHP

Ruby

Haskell

Rust

CONTROL

SAFETY

YOUR

INTENT

The Secret Sauce

Ownership and Borrowing

Explaining your intent at compile-time

Hello World

fn main() {
    println!("Hello world!");
}

Functions

fn sum(a: i32, b: i32) -> i32 {
    a + b
}

fn main() {
    let answer = sum(2, 3);
    println!("Result is {:?}", answer);
}

Machine Types

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

Primitive Types

  • Tuples: (i32, u64)
  • Unit type: ()
  • Booleans: bool

Structs

struct Point {
    x: i32,
    y: i32,
}

fn main() { 
    let p = Point { x: 3, y: 4 };

    println!("Point x: {:?}, y: {:?}", p.x, p.y); 
}
fn get_area(point: Point) -> i32 {
    point.x * point.y
}

fn main() { 
    let p = Point { x: 3, y: 4 };
    let area = get_area(p);
    
    println!(
        "Point x: {:?}, y: {:?}, area: {:?}", 
        p.x, p.y, area
    );
}
fn get_area(point: Point) -> i32 {
    point.x * point.y
}

fn main() { 
    let p = Point { x: 3, y: 4 };
    let area = get_area(p);
    
    println!(
        "Area: {:?}", 
        area
    );
}

OK!

struct Point {
    x: i32,
    y: i32,
}

impl Point {
    fn to_area(self) -> i32 {
        self.x * self.y
    }
}

fn main() { 
    let p = Point { x: 3, y: 4 };
    println!(
        "Area: {:?}", 
        p.to_area()
    );
}

Impls

Ownership System

Ownership & Mutability

RAII

use std::net::TcpStream;

fn main () {
    let _stream = 
        TcpStream::connect("127.0.0.1:8080").unwrap();

    //...
} // the stream is closed here

Heap

fn main() { 
    let p = Point { x: 3, y: 4 };
    let b = Box::new(p);

    println!("Point x: {:?}, y: {:?}", b.x, b.y); 
}

String

fn main() { 
    let mut s = String::new();
    s.push_str("World!");

    println!("Hello {}", s); 
}

Vec

fn main() { 
    let mut items = vec!["Hello"];
    items.push("World!");

    println!("{}", items.connect(" "));
}

Closures

fn main() { 

    let double_me = |x| x + x;
    

    let double_us = |x, y| (double_me(x), double_me(y));
    
    println!("{:?}", double_us(4, 30));


    let double_small_number = 
        |x| if x > 100 { 
            x 
        } else {
            x * 2
        };
        
    println!("small doubled: {:?}", double_small_number(42));
    println!("large doubled: {:?}", double_small_number(1337));
}

1

2

3

Thread

use std::thread;

fn main() {
    let a = 2;
    let t = thread::spawn(
        move || a * 2
    );
    
    println!("{:?}", t.join()); // Ok(4)
}

Threads

use std::thread;

fn main() { 
    let items = vec!["Functional", "Vilnius", "Meetup"];
    let mut threads = Vec::new();   

    for item in items {
        threads.push(
            thread::spawn(
                move || item.len()
            )
        );
    }

    let mut results = Vec::new();

    for thread in threads {
        results.push(thread.join());
    }

    println!("{:?}", results); // [Ok(10), Ok(7), Ok(6)]
}

Functional -ish Threads

use std::thread;

fn main() {
    let items = vec!["Functional", "Vilnius", "Meetup"];
    
    let threads: Vec<_> = items.into_iter()
        .map(
            |item| thread::spawn(
                move || item.len()
            )
        )
        .collect();
            
    let results: Vec<_> = threads.into_iter()
        .map(
            |thread| thread.join()
        )
        .collect();

    println!("{:?}", results); // [Ok(10), Ok(7), Ok(6)]
}

Channels

use std::thread;
use std::sync::mpsc::channel;

fn main() {
    let (tx, rx) = channel();
    
    for i in 0..3 {
        let tx = tx.clone();
        
        thread::spawn(
            move || tx.send(i).unwrap()
        );
    }
    
    println!("{:?}", rx.recv()); // Ok(2)
    println!("{:?}", rx.recv()); // Ok(0)
    println!("{:?}", rx.recv()); // Ok(1)
}

Borrowing System

Immutable borrow:

let a = 42;
let ref = &a;

Mutable borrow:

let mut a = 42;
let ref = &mut a;

Only Aliasing

Only Mutation

Iterator Invalidation

fn main() { 
    let mut items = vec![1, 2, 3];
    
    for item in items { // error
        items.clear();
    }
}
<anon>:5:9: 5:14 error: use of moved value: `items`
<anon>:5         items.clear();
                 ^~~~~

<anon>:4:17: 4:22 note: `items` moved here because it has type 
                        `collections::vec::Vec<i32>`, which is non-copyable

<anon>:4     for item in items {
                         ^~~~~
fn main() { 
    let mut items = vec![1, 2, 3];
    
    for item in &items { // error
        items.clear();
    }
}
<anon>:5:9: 5:14 error: cannot borrow `items` as mutable because it is also 
                        borrowed as immutable
<anon>:5         items.clear();
                 ^~~~~

<anon>:4:18: 4:23 note: previous borrow of `items` occurs here; the immutable 
                        borrow prevents subsequent moves or mutable borrows 
                        of `items` until the borrow ends
<anon>:4     for item in &items {
                          ^~~~~
fn main() { 
    let mut items = vec![1, 2, 3];
    
    for item in &mut items { // error
        items.clear();
    }
}
<anon>:5:9: 5:14 error: cannot borrow `items` as mutable more than 
                        once at a time
<anon>:5         items.clear();
                 ^~~~~

<anon>:4:22: 4:27 note: previous borrow of `items` occurs here; the 
                        mutable borrow prevents subsequent moves, borrows, 
                        or modification of `items` until the borrow ends
<anon>:4     for item in &mut items {
                              ^~~~~
impl Vec {
    fn clear(&mut self) { 
        ... 
    }
}

Profit!

  • No data races
  • No dangling pointers
  • No segfaults
  • No read of uninitialized memory
  • Non UTF-8 sequences in String
  • No mutating immutable data
  • No Garbage Collector necessary
  • No runtime
  • Compile-time guarantees

However...

  • Can still deadlock
  • Object cycles can still memory leak
  • Integer overflow is user error
  • Can exit without calling destructor

 

  • There is a little keyword called unsafe

System Calls

#[link(name = "cpuid")]
extern {
    pub fn cpuid_get_raw_data(raw: *mut cpu_raw_data_t) -> c_int;
}

pub fn identify() -> Result<CpuInfo, String> {
    let mut raw: ffi::cpu_raw_data_t = Default::default();
    let raw_result = unsafe {
        ffi::cpuid_get_raw_data(&mut raw)
    };
    if raw_result != 0 {
        return Err(error());
    }
    
    ...

    Ok(CpuInfo {
        num_cores: data.num_cores as int,
        num_logical_cpus: data.num_logical_cpus as int
    })
}
struct Point {
    x: i32,
    y: i32,
}

impl Point {
    fn to_area(self) -> i32 {
        self.x * self.y
    }
}

fn main() { 
    let p = Point { x: 3, y: 4 };
    println!(
        "Area: {:?}", 
        p.to_area()
    );
}

Back to the point...

struct Point {
    x: i32,
    y: i32,
}

impl Point {
    fn get_area(&self) -> i32 {
        self.x * self.y
    }
}

fn main() { 
    let p = Point { x: 3, y: 4 };
    println!(
        "Area: {:?}", 
        p.get_area()
    );
}
struct Point {
    x: i32,
    y: i32,
}

impl Point {
    fn offset_x(&mut self, val: i32) {
        self.x += val;
    }
}

fn main() { 
    let mut p = Point { x: 3, y: 4 };
    
    p.offset_x(2);

    println!("X: {:?}", p.x);
}
struct Rect {
    x: f32,
    y: f32,
}

trait GetArea {
    fn get_area(&self) -> f32;
}

impl GetArea for Rect {
    fn get_area(&self) -> f32 {
        self.x * self.y
    }
}

fn main() { 
    let r = Rect { x: 3.0, y: 4.0 };

    println!("Area: {:?}", r.get_area());
}

Traits

struct Circle {
    r: f32,
}

impl GetArea for Circle {
    fn get_area(&self) -> f32 {
        PI * self.r * self.r
    }
}

impl Circle {
    fn larger_than(&self, other: &Rect) -> bool {
        self.get_area() > other.get_area()
    }
}

fn main() { 
    let r = Rect { x: 3.0, y: 4.0 };
    let c = Circle { r: 3.0 };

    if c.larger_than(&r) {
        println!("Circle is larger!");
    } else {
        println!("Rectangle is larger!");
    }
}
struct Circle {
    r: f32,
}

impl GetArea for Circle {
    fn get_area(&self) -> f32 {
        PI * self.r * self.r
    }
}

impl Circle {
    fn larger_than<T: GetArea>(&self, other: &T) -> bool {
        self.get_area() > other.get_area()
    }
}

fn main() { 
    let r = Rect { x: 3.0, y: 4.0 };
    let c = Circle { r: 3.0 };

    if c.larger_than(&r) {
        println!("Circle is larger!");
    } else {
        println!("Rectangle is larger!");
    }
}
trait GetArea {
    fn get_area(&self) -> f32;
    
    fn larger_than<T: GetArea>(&self, other: &T) -> bool {
        self.get_area() > other.get_area()
    }
}

// struct Rect { ...
// impl GetArea for Rect { ...
// struct Circle { ...
// impl GetArea for Circle { ...

fn main() { 
    let r = Rect { x: 3.0, y: 4.0 };
    let c = Circle { r: 3.0 };

    if c.larger_than(&r) {
        println!("Circle is larger!");
    } else {
        println!("Rectangle is larger!");
    }
}

Operator Traits

use std::ops::{ Add, Sub, Neg };

impl Add for Point {
    type Output = Point;
    
    fn add(self, other: Point) -> Point {
        Point { x: self.x + other.x, y: self.y + other.y }
    }
}

impl Neg for Point {
    type Output = Point;
    
    fn neg(self) -> Point {
        Point { x: -self.x, y: -self.y }
    }
}

impl Sub for Point {
    type Output = Point;
    
    fn sub(self, other: Point) -> Point {
        self + (-other)
    }
}

1

2

3

Traits Everywhere

  • Arithmetic (Add, Sub, Neg, ..)
  • Iterators, Indexing, Range
  • Parsing (FromStr, ..)
  • Formatting (ToString, Debug, Display, ..)
  • Closures are traits (Fn, FnMut, FnOnce)
  • IO (Cursor, Read, Write, ..)
  • Memory Management (Deref, Cow, Borrow, ..)

Enum Types

enum Color {
    Rgb((u8, u8, u8)),
    Undefined,
}

fn main() { 
    let color = Color::Rgb((33, 44, 55));
    
    match color {
        Color::Rgb((r, g, b)) => {
            println!("{}, {}, {}", r, g, b);
        },
        _ => {
            println!("Nothing!");
        },
    }
}

Result Types

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

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

fn main() {
    match maybe_result = computation() {
        Ok(result) => println!("{:?}", result),
        Err(e) => println!("Error! {:?}", e),
    }
}
use std::fs::File;
use std::io::{ BufReader, BufRead };

fn main() {

    if let Ok(file) = File::open("foo.txt") {

        let sum = BufReader::new(file)
            .lines()
            .filter_map(|maybe_line| maybe_line.ok())
            .filter_map(|line| line.parse().ok())
            .fold(0, |acc, i: i32| acc + i);

        println!("Sum is {:?}.", sum);
    }

}

Reading File

let action = match time_left() {
    Some(time) => time.to_demo(),
    _ => None,
}

Rust tools

  • Package manager, cargo, is awesome!
  • Find rust libraries (called crates) on crates.io;
  • Built-in tests!
  • Built-in documentation generator!
  • Your favorite editor has a plugin that provides at least the syntax highlighting, and at most - code completion and debugging.

Lithuanian Rust Developers @ LinkedIn

rust-lang.org

play.rust-lang.org

crates.io

Questions?

@nercury, @fpvilnius