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
furiousdangerous
- Fast and
- 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
- 41