Introduction to Rust

Copenhagen Tech Polyglot meetup

march 18th, 2015

Who am I?

  • Andor Uhlár
  • Backend dev at Robocat 😸
  • FOSS entusiast

How I use Rust

  • Backed services
    • Replaced Python
  • One-off utilities
  • and a lot more!

Keep these in mind

Rust 1.0.0-alpha2

All code is safe, except where otherwise noted

unsafe {}

The Rust Book!

Hello World!

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

function

name

macro

string

The basics

Variable bindings

  • Immutable by default
  • Left-hand side is a pattern
  • Local type inference
let x = 5;
let y = 3.14;
let is_rust_fun = true;


// Immutable, erroneous
let foo = 1;
foo = 2;
// All good!
let mut bar = 10;
bar = 30;
// Redefinition is also allowed
let foo = 1;
let foo = 2;


// Error
let a = b = 1;
// Valid
let (a, b) = (1, 2);


// Type inference is cool
let c: int = 42;

Primitive types

  • Integer types
    • signed-unsigned
    • size: 8, 16, 32, 64 bits, machine-sized
  • Floating-point
    • f32, f64
  • Booleans
  • Static array
    • Compile-time sized!
// Integers
let a = 1u8;      // unsigned, 8-bit
let b = 2i16;     // signed, 16-bit
let c = 23u64;    // unsigned, 64-bit
let d = 100isize; // signed, machine-sized

// i8, i16, i32, i64
// u8, u16, u32, u64
// isize, usize

// deprecated
let e = -200int;  // signed, same as isize
let f = 0uint;    // unsigned, same as usize


// Floats
let pi  = 3.1415f32;
let phi = 1.1618f64;
let tau = 6.2831;

// Boolean
let rust_rocks = true; // type bool
let or_not = false;

// Static array
let fib   = [1, 1, 2, 3, 5, 8]; // [int; 6]
let zeros = [0; 10];            // [int; 10];

More primitive types

  • Tuples
// Tuples
let coords   = (10, 20);
let nothing  = (); // unit or zero-tuple
let today    = (2015, 3, 18);
let (year, month, day) = today; // destructuring
let year = today.0;
let month = today.1;



// Slices are denoted by [T]
// eg. [u8], a byte slice
  • Slices
    • view into memory
    • continuous
    • can't manually make one
    • "dynamic array"
  • Strings a bit later

Blocks

  • Expression-based
    • everything is an expression (terms and conditions 😋)
    • value of block is value of last statement
  • Usual scoping rules
    • Shadowing important
// Last expression in block
let x = 5;
let y = {
    let z = x * 3;
    z - 2
};

// () is a perfectly valid value
let a = {
    let b = 10;
};
// a will be ()


// Usual scoping
let a = 10;
{
    let b = 10;
}
let c = a + b; // Oops!

// Shadowing
let a = 10;
let b = {
    let a = 20;
    a * 2
};
// b will be 40, not 20

If branching

  • Parentheses optional
  • Curly braces required
if foo == 42 {
    // do some work
}


if bar + 10 == 84 {
    // true branch
} else {
    // false branch
}


if a == 1 {
    // 1 case
} else if a == 2 {
    // 2 case
} else if a == 3 {
    // 3 case
} else {
    // other cases
}

If cont.

  • Strictly boolean condition
let x = 1;
// Nope
if x {
    
}
// Good
if x == 1 {

}


// if is also an expression
let a = if planets_aligned { 1 } else { 2 };

// Types must match! This is an error.
let b = if true { 1 } else { 2.0 };

let c = if x % 2 == 0 {
    x * 2
} else {
    x + 1
};

let d = if foo {
    c * 2; // <- common mistake
} else {
    //...
}
  • Blocks still evaluate to last expression

Match

  • "switch on steroids"
    • very powerful
  • No fall-through
  • No unreachable cases
    • overlaps are ok, though
  • Guard blocks
    • if something...
  • Still expression-based
  • To be continued!
match x {
    1 => ...,
    2 => ...,
    3 => ...,
    _ => ...,
}

match y {
    x if x % 2 == 0 => x - 1,
    x if x % 2 == 0 && x > 100 => x + 3,
    ...
}

let y = match x {
    a if a < 0 => -a,
    b if b > 100 => b - 100,
    c => c
};

Functions

  • Nothing exotic
  • Type inference local only
  • Parameter list
    • between parens
    • name: type
    • comma-separated
  • Return type
    • Single return type
    • Tuples are cool
    • Implicit ()
// fn keyword, name
fn laugh() {
    // body
    println!("ha ha!");
}

fn main() {
    // invocation
    laugh(); 
    laugh(); 
    laugh();
}


fn double(x: int) -> int {
    x * 2
    // return x * 2;
    // both are fine and equivalent
}

fn add(a: int, b: int) -> int {
    return a + b;
}

fn foo(x: int) -> (int, int) {
    (x + 1, x - 1)
}

fn main() {
    let four          = double(2);
    let three         = add(1, 2);
    let (twelve, ten) = foo(11);
}

Functions cont.

  • Diverging types
  • Just think of it as "doesn't return" for now
fn diverging() -> ! {

}

fn main() {
    diverging(); // this call will never return
}

Structs

  • Classic, C-like structs
  • Declaration
    • name: type,
  • Initialization
    • name: value,
  • Mutability transient
struct Date {
    year: int,
    month: int,
    day: int
}

struct Meetup {
    number_of_attendees: int,
    location_coords: (f32, f32),
    date: Date
}

fn main() {
    let today = Date { year: 2015, month: 3, day: 18 };
    let polyglots = Meetup { 
        number_of_attendees: 35,
        location_coords: (12.34, 56.78),
        date: today
    };
    polyglots.number_of_attendees = 34; // not ok!
    polyglots.date.day = 20;            // not ok!
    let mut polyglots = polyglots;
    polyglots.date.day = 21;            // ok now
}

Tuple- and unit structs

  • Named tuples
  • 1-tuple
    • Newtype
    • Wrapper type
  • Unit struct
struct Foo(i32, f64);
struct Bar(bool, (i32, i32));

// Different types!

struct Meter(f32);
struct Feet(f32);

struct Placeholder;

fn main() {
    let my_height = Meter(1.89);
    let mut other = Feet(6.4);

    other = my_height; // uh-oh!

    let placeholder = Placeholder;
}

Enums

  • Pick a name
    • sum type
    • discriminated union
    • disjoint union
    • variant type
    • and many others...
  • Variants can be
    • unit-like
    • tuple-like
    • struct-like
  • Can mix and match
enum Icecream {
    Vanilla,
    Chocolate,
    Strawberry
}

enum Shape {
    Circle(f32),         // radius
    Square(int),         // side
    Rectangle(int, int), // width and height
}

enum Foo {
    Bar,
    Baz(int, f64, (bool, u32)),
    Qux {
        a: u8,
        b: u16,
        c: u32
    }
}

// Generics woo!
enum Option<T> {
    Some(T),
    None
}

Enums cont.

  • How to use them...
enum Icecream {
    Vanilla,
    Chocolate,
    Strawberry
}

let my_favorite = Icecream::Vanilla;

if your_favorite == my_favorite {
    // we're best buddies!
}

enum Shape {
    Circle(f32),         // radius
    Square(int),         // side
    Rectangle(int, int), // width and height
}

let foo = Shape::Circle(100.0);
let window = Shape::Rectangle(200, 300);

Patterns!

Match with patterns

  • It's really powerful
enum Shape {
    Circle(f32),         // radius
    Square(int),         // side
    Rectangle(int, int), // width and height
}

fn area(shape: Shape) -> f32 {
    match shape {
        Circle(0) | Square(0)    => 0,
        Circle(radius)           => 3.1415 * radius * radius,
        Square(side)             => side * side,
        Rectangle(width, height) => width * height
    }
}

fn main() {
    let a = Shape::Circle(100.0);
    let area_of_my_circle = area(a);
    
    let b = Shape::Square(50);
    let other_area = area(b);
}

Match with patterns

  • Not just enums
// Tuples work just fine
let x = (1, 2, 3, 4);

match x {
    (1, a, b, 10) => ...,
    (5, 10, 9, 2) => ...,
    (a, b, 10, c) if a + b == c => ...,
    (_, 2,  3, _) => ...,
    _ => ...
}


// Structs too!
struct Foo { a: int, b: int }
let x = Foo { a: 10, b: 20 };

match x {
    Foo { a: 30, b: 40 } => ...,
    Foo { b: 10, .. }    => ...,
    Foo { a, b: 50 }     => ...,
}

impl blocks

methods

impl block

  • List of functions
    • occasionally something else
  • Types
    • static (no self)
    • borrowing (&self)
    • mutably borrow (&mut self)
    • moving (self)
  • new is not special, just a common idiom
struct Foo {
    a: int,
    b: int
}

impl Foo {    
    fn new() -> Foo {
        Foo {
            a: 0,
            b: 0
        }
    }

    fn a_plus_b(&self) -> int {
        self.a + self.b
    }

    fn set_a(&mut self, new_a: int) {
        self.a = new_a;
    }

    fn set_b(&mut self, new_b: int) {
        self.b = new_b;
    }
}

fn main() {
    let mut foo = Foo::new();
    foo.set_a(10);
    foo.set_b(20);
    let x = foo.a_plus_b();
}

Borrow? Mutable borrow? wtf?!

  • Borrow
    • method borrows value for the time of the call
  • Immutable
    • I can look at it
  • Mutable
    • I can look at it and change it
struct Foo {
    a: int,
    b: int
}

impl Foo {
    //...

    fn a_plus_b(&self) -> int {
        // I am immutably borrowing it
        self.a + self.b
    }

    fn set_a(&mut self, new_a: int) {
        // notice mut, I am mutably borrowing
        self.a = new_a;
    }

    //...
}

Borrowing and moving

  • Borrow only for the time of the function call
  • Moving takes ownership of the value (it consumes the value)
struct Foo {
    a: int
}

fn eat_foo(foo: Foo) {
    // I own foo now
}

fn borrow_foo(&foo: Foo) {
    // I am immutably borrowing foo
}

fn mut_borrow_foo(&mut foo: Foo) {
    // I am mutably borrowing foo
}

fn main() {
    let foo = Foo { a: 10 };
    borrow_foo(&foo); // borrow foo immutably
    mut_borrow_foo(&mut foo); // borrow foo mutably

    eat_foo(a);    // <- consumed here
    let b = foo.a; // error, foo moved (consumed)
    // it is no longer valid to refer to foo
}
  • Demonstrated with functions, same applies to methods
    • Borrowing is implicit with methods though

Arrays, vectors, slices

Remember arrays?

let a = [1, 2, 3, 4];
// [int; 4]

let b = [(1, 2), (3, 4)];
// [(int, int), 2];

Enter slices

let a = [1, 2, 3, 4];
// [int; 4];

// All of these are &[int]
let b = &a[..];   // Slice into the entire array
let c = &a[1..];  // Slice from first element
let d = &a[2..3]; // Slice from second to third element
  • Same idea as value borrowing
    • Borrowing contents of array
  • Don't know the size
  • But it is bounds-checked

Vectors

  • Slices and static arrays aren't very useful on their own
  • Standard library type, not special in any way
let mut numbers = Vec::new();
numbers.push(1);
numbers.push(2);
numbers.push(3);

let slice = &numbers[0..2];

// Macro for initializing a Vec
let other_numbers = vec![4, 5, 6];

Looping

  • 3 kinds of loops
    • They're all the same, really
  • break and continue as expected
while condition {
    // ...
}

loop {
    // same as while true
}

for elem in iter {
    // ...
}

Iterators

let numbers = vec![1, 2, 3, 10, 20];
let mut sum = 0;

for elem in numbers {
    sum += elem;
}
  • Iterating a Vector
let numbers = vec![1, 2, 3, 10, 20];
let numbers = &numbers[2..];
let mut sum = 0;

for elem in numbers {
    sum += elem;
}
  • Or a slice

Strings

they're a little special...

String vs &str vs char

  • String is owned string type, equivalent of Vec
  • &str is string slice, same idea as regular slice
  • char is singular character
  • Strings (String, &str, char) are guaranteed valid UTF8
    • char is a Unicode code point
  • They aren't just a bunch of bytes
    • O(n) access
let a = "foo";             // &'static str
let b = "bar".to_string(); // String
let c = 'h';

for byte in b.bytes() {
    // iterate through individual bytes
    // not very useful most of the time
}

for char in b.chars() {
    // iterate through code points
}

for grapheme in b.graphemes() {
    // This is what you'll want most of the time
    // iterate through graphemes
}

String literals

let a = "foo";    // Simple string literal
let a = "\t\n";   // The usual escape sequences
let a = "\u{56}"; // Unicode escape

// Bytestrings, type is &[u8]
let bytestring = b"abcdef\x12\x00"; // Doesn't have to be valid UTF8

// Raw strings, they don't escape anything
// r, followed by variable number of #s, and a "
// closed by a " plus an equal number of #s
let rawstring r###"hello world """ # foobar ""###;
// this will be:
// hello world """ # foobar "

Traits

Traits

  • Similar in concept to Haskell typeclasses
    • Java/C# interface (but more powerful)
  • Define a shared set of behaviour
struct Circle {
    radius: f32
}

struct Rectangle {
    width: f32,
    height: f32
}

trait HasArea {
    fn area(&self) -> f32;
}

impl HasArea for Circle {
    fn area(&self) -> i32 {
        self.radius * self.radius * 3.1415
    }
}

impl HasArea for Rectangle {
    fn area(&self) -> i32 {
        self.width * self.height
    }
}

Traits cont.

  • Can be implemented for any type
  • Can provide default implementations for functions
  • Can get really complex, this is just a brief intro
trait HasArea {
    fn area(&self) -> f32;

    fn double_area(&self) -> f32 {
        area() + area()
    }
}

impl HasArea for i32 {
    fn area(&self) -> f32 {
        self + self
    }
}

impl HasArea for () {
    fn area(&self) -> f32 {
        0.0
    }
}

impl<T: HasArea> HasArea for (T, T) {
    fn area(&self) -> f32 {
        self.0.area() + self.1.area()
    }
}

Using traits

  • Generics
    • Monomorphized
    • Statically dispatched
  • Trait object
    • Dynamically dispatched
trait HasArea {
    fn area(&self) -> f32;
}

fn generic_area<T: HasArea>(x: T) {
    // I can do this since I've restricted T
    // This function accepts Ts that implement HasArea
    let area = x.area();
    // This will be resolved at compile time
    // and statically dispatched
}

fn dynamic_area(x: &HasArea) {
    let area = x.area();
    // Unknown, this will be dynamically dispatched
}

Generic vs dynamic dispatch

  • Generics
    • Monomorphized
    • Statically dispatched
  • Trait object
    • Dynamically dispatched
fn generic<T: SomeTrait>(x: T) {}
fn dynamic(x: &SomeTrait) {}

fn main() {
    // I have some concrete T
    // That implements SomeTrait
    generic(foo);

    // I have for example a collection
    // in the form &[&SomeTrait]
    // Concrete type is only known at runtime
    for foo in foos {
        dynamic(foo);
    }
}

Module system

Module example

mod foo {
    fn bar() {}
}

fn main() {
    foo::bar();  // error, bar is private
}
mod foo {
    pub fn bar() {}
}

fn main() {
    foo::bar();  // cool, bar is public
}

Same with everything else (structs, enums, traits...)

Reexports, use, super, self

use foo::bar;

mod foo {

    pub use self::qux::bar;

    mod qux {
        use super::baz;
        pub fn bar() { baz(); }
    }

    pub fn baz() {}
}

fn main() {
    bar();
}

Different use-s

// Bring things into scope

use foo;              // use module
use foo::bar;         // use inner module or item (function, struct, trait, etc.)
use foo::{bar, baz};  // pick a list of them
use foo::{self, bar}; // bring bar AND self, meaning foo here
use foo::bar as qux;  // bring in under a different name
use foo::*;           // glob import, generally not a good idea

Structure

// main.rs
mod foo;

fn main() {
    // ...
}
// foo.rs

pub mod bar {

}
// foo/mod.rs

// Will include foo/bar.rs
pub use bar;

Either, but not both

Executable vs Library

  • main.rs (+ fn main) => executable
  • lib.rs => library

Becomes a crate

.exe/executable, .so, .dylib, .rlib

Cargo

Build system and dep manager

Cargo.toml

  • Dependencies
  • Build information
  • Should be checked into VCS

Cargo.lock

  • Auto-generated by Cargo after every successful build
    • Dependencies
    • State of the world
  • Should only be checked into VCS for executables
  • Community crate collection
  • Cargo supports regular git repos too

Thanks for listening!

rust-introduction

By Andor Uhlár

rust-introduction

Andor Uhlár's Introduction to Rust presentation for Copenhagen Tech Polyglots meetup

  • 551
Loading comments...