Espen Henriksen

Senior Engineer

Statens Kartverk

esphen_

esphen

espen.dev

Rust

And why the world needed another programming language

Story time

Once upon a time

  • Mozilla Firefox was written in C++ mostly
  • Threads and races
  • Errors mostly memory mgmt. and pointers
  • Graydon Hoare's hobby between 2006-2009
  • Developed in open: 2010
  • 1.0 (stable) in 2015
  • 2020 mozilla layoffs
  • Governance moved to Rust Foundation in 2021

Why another language?

  • C & C++
    • Fast and furious dangerous
  • Python
    • Ergronomic and safe but slow
  • Java
    • Safe and fast, but not ergronomic
  • Most languages are dated

Why is it awesome?

Modern syntax

Speeeeeed!

Source: benchmarksgame-team.pages.debian.net

Minimal runtime

Strongly typed

  • Like java, C, etc.
  • Type inference
  • Provides memory safety guarantees
  • Allows for optimisations
  • Automatically

No garbage collection

  • GC: Allocations live on stack/heap until GC does free on unused allocation
  • Like C, Rust has no GC
  • Unlike C no manual free
    • RAII
  • No runtime overhead
  • No "spikes" in memory
  • Reference Counting exists
  • Third party garbage collection exists

Fearless Concurrency

  • Memory safety solved concurrency problems
  • With ownership and type checking, many concurrency errors are compile-time errors
  • Message passing and shared-state with mutex
fn message_passing_example() {
  let (tx, rx) = mpsc::channel();

  thread::spawn(move || {
    let val = String::from("hi");
    tx.send(val).unwrap();
  });

  let received = rx.recv().unwrap();
  println!("Got: {}", received);
}

fn mutex_example() {
  let counter = Mutex::new(0);
  let mut handles = vec![];

  for _ in 0..10 {
    let handle = thread::spawn(move || {
      let mut num = counter.lock().unwrap();

      *num += 1;
    });
    handles.push(handle);
  }

  for handle in handles {
    handle.join().unwrap();
  }

  println!("Result: {}", *counter.lock().unwrap());
}

Why is it different?

Immutability

  • Everything immutable by default
  • Can only change variables marked with mut
  • References can be mutable or read-only
fn main() {
  let i = 1;
  i = i + 5; // Compilation error
  
  let mut j = 1;
  j = j + 5; // OK!
  
  let foo = Foo::new();
  let mut k = &foo;
  k.foo = "Hi" // Compilation error!
  
  let mut l = &mut foo;
  k.foo = "Hi" // Ok!
}

No null!

  • Nothing can ever be null!
  • "Introducing null was my billion dollar mistake" - Tony Hoare
  • Values can be None when wrapped with Option<T>
  • Values must be unwrapped from Option
    • .unwrap()
    • match
    • ? - Shorthand for unwrap or return error
    • if let
// Vars are always defined
// foo can at no point be null
let foo = 5

// Create Option<T> variables
// Notice type inference
let some_number = Some(5);
let some_string = Some("a string");

let absent_number: Option<i32> = None;

// Get values from Option<T>
let number1 = some_number.unwrap();
let number2 = some_number.unwrap_or(0);
let number3 = some_number.unwrap_or_default();
let number4 = some_number?;

match number {
  Some(i) => println!("value: {}", i),
  None => eprintln!("It was None"),
}

if let Some(s) = some_string {
  println!("value: {}", s);
}

Error handling

  • Built in Result<T, Error> type
  • Like Option<T>, but will also include error
  • Unrecoverable errors: panic!
fn can_fail() -> Result<string, Error> {
  // In this example case we failed
  let success = false;
  
  if success {
    Ok("It worked!")
  } else {
    Err("Oh no!") 
  }
}

fn main() {
  let result = can_fail();
  match (result) {
    Ok(value) => {
      println!("The value was: {}", value);
    },
    Err(err) => {
      panic!("It failed! Error: {}", err);
    },
  }
}

Great with threads

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

fn main() {
    let (tx, rx) = mpsc::channel();

    thread::spawn(move || {
        let val = String::from("hi");
        tx.send(val).unwrap();
    });

    let received = rx.recv().unwrap();
    println!("Got: {}", received);
}

Unsafe code

Macros

  • Writing code that writes code
    • "Metaprogramming"
    • Code generation
  • Different types
    • Derive macros
    • Attribute-like macros
    • Function-like macros
  • Makes code ergronomic
  • Allows for custom syntax
    • vec![0; 10]
// Add features from a trait to a struct
#[derive(Copy)]
struct Pancakes;

#[route(GET, "/")]
fn index() {}

// The function is only included in the
// build when compiling for macOS
#[cfg(target_os = "macos")]
fn macos_only() {}

// The function is only included in the
// build when compiling for Windows
#[cfg(target_os = "windows")]
fn win_only() {}

fn main() {
  println!("Hello from the println macro");
  
  let array = vec![1, 2, 3];

  let config_cirectory = if cfg!(windows) {
    win_only()
  } else {
    macos_only()
  };
}

Futures

  • Built-in support for async programming
  • Zero cost abstraction
    • No allocation
    • No synchronization costs
  • Requires future runtime like tokio
    • "Minimal overhead"
  • Futures and Streams

Async/Await

  • Built in support for async programming
  • Optimises thread usage
  • Different syntax
    • .await
    • .await?
    • Allows chaining
async fn get_from_server() -> Result<Response, Error> {
  reqwest::get(
    "https://www.rust-lang.org"
  ).await?
}

async fn main() {
  // Future<Result<Response, Error>>
  let future = hello_world();
  let response = future.await?;
  println!("The response was {}", response);
}

Built-in docs

  • Doc comments
  • Module doc comments
  • Generate docs for dependencies also
  • Powerful search
  • cargo doc --open

Tests

  • Built-in unit test suite
    • cargo test
  • Uses macros for assertions
    • assert!(bool)
    • assert_eq!(any, any)
    • assert_ne!(any, any)
  • Documentation tests
    • Ensures examples in doc is up to date
    • Works as normal tests
/// Adds a number to another number
/// ```
/// let result = add(1, 2);
///
/// assert_eq!(result, 3);
/// ```
fn add(a: i32, b: i32) -> i32 {
  a + b
}

#[cfg(test)]
mod tests {
  #[test]
  fn it_works() {
    let result = crate::add(2, 2);
    assert_eq!(result, 4);
  }
    
  #[test]
  fn add_negative_number() {
    let result = crate::add(-1, 2);
    
    // Custom error message
    assert_eq!(
      result,
      1,
      "Adding negative numbers failed. Was: {}",
      result
    );
  }
}

Integrates with C

  • Foreign Function Interface (FFI)
  • Allows calling C from Rust and vice versa
  • Rust CXX library to allow same for C++
  • FFI for JavaScript when writing WebAssembly

The biggest hurdles

The borrow checker

  • Safety guarantees without garbage collection
  • RAII from C++
  • Scoped references
  • 3 ways to pass values around
    • Copying
    • Move ownership
    • References
      • &mut and &
      • Only one mut ref

Example - Does this run?

fn hold_my_vec<T>(_: Vec<T>) {}

fn main() {
    let v = vec![2, 3, 5, 7, 11, 13, 17];
    hold_my_vec(v);
    let element = v.get(3);

    println!("I got this element from the vector: {:?}", element);
}

NO!

error[E0382]: borrow of moved value: `v`
--> src/main.rs:6:19
          |
        4 |     let v = vec![2, 3, 5, 7, 11, 13, 17];
          |         - move occurs because `v` has type `std::vec::Vec<i32>`, which does not implement the `Copy` trait
        5 |     hold_my_vec(v);
          |                 - value moved here
        6 |     let element = v.get(3);
          |                   ^ value borrowed here after move
fn hold_my_vec<T>(_: Vec<T>) {}

fn main() {
    let v = vec![2, 3, 5, 7, 11, 13, 17];
    hold_my_vec(v);
    let element = v.get(3);

    println!("I got this element from the vector: {:?}", element);
}

Lifetimes

Lifetimes

Can be confusing, but you got this!

Why do I care?

Used a lot!

  • Large companies
    • Google, Microsoft, Amazon, Facebook..
    • Firefox, Discord, Figma, npm, Dropbox, AWS (S3, EC2, lambda)..
  • Growing rapidly everywhere
  • Fastest growing language in 2021 - SlashData

Used in

  • Web servers
  • CLI programs
  • WebAssembly
  • Embedded software
  • Networking and general async programming
  • Machine Learning
  • Game development
  • Security critical apps (cryptography, blockchain)
  • GUI apps (emerging)
  • Possible second language in kernel development

Awesome ecosystem

  • Cargo
    • Cargo.toml
    • Cargo.lock
  • Rust CLI
    • rustc
    • rustup
  • Clippy
  • Rustfmt
  • And much more at crates.io

Loved

  • Most loved programming language every year since 2016
  • Developer experience is important
  • Reflects on code readability, potentially infers fewer bugs

Well paid

  • Shortage of Rust developers
  • Jobs generally pay well
  • Few jobs in Norway (for now) but many remote jobs available
    • Telenor
    • E-tjenesten

Cool libraries

Rocket

Diesel

reqwest

cargo-edit & cargo plugins

Tokio

  • You will probably see tokio at some point
  • Async runtime for async/await
  • Main async runtime currently
  • #[tokio::main] macro enables async features
  • "Applications can process hundreds of thousands of requests per second with minimal overhead"
use mini_redis::{client, Result};

#[tokio::main]
async fn main() -> Result<()> {
  // Open a connection to the mini-redis address.
  let mut client = client::connect(
    "127.0.0.1:6379"
  ).await?;

  // Set the key "hello" with value "world"
  client.set("hello", "world".into()).await?;

  // Get key "hello"
  let result = client.get("hello").await?;

  println!("got value from the server; result={:?}", result);
  Ok(())
}

How do I get started?

Great learning resources

rustup.rs

Fin

https://slides.com/esphen/rust-intro

Intro to Rust

By Eline H

Intro to Rust

  • 44