Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Rust Code Drills

This chapter is the practical companion to the oral questions. The goal is not to read these once. The goal is to retype them from memory until the patterns become automatic.

Ownership and Borrowing

Move

fn main() {
    let s1 = String::from("hello");
    let s2 = s1;

    println!("{s2}");
}

Shared borrow

fn len_of(s: &String) -> usize {
    s.len()
}

fn main() {
    let s = String::from("hello");
    let n = len_of(&s);
    println!("{s} {n}");
}

Mutable borrow

fn append_world(s: &mut String) {
    s.push_str(" world");
}

fn main() {
    let mut s = String::from("hello");
    append_world(&mut s);
    println!("{s}");
}

Shared reads or one writer

fn main() {
    let mut s = String::from("hello");

    let r1 = &s;
    let r2 = &s;
    println!("{r1} {r2}");

    let r3 = &mut s;
    r3.push('!');
    println!("{r3}");
}

Ownership move into function

fn consume(s: String) {
    println!("{s}");
}

fn main() {
    let s = String::from("hello");
    consume(s);
}

Types, Values, and Strings

String vs &str

fn greet(name: &str) {
    println!("hello, {name}");
}

fn main() {
    let owned = String::from("bobby");
    let borrowed: &str = &owned;

    greet(&owned);
    greet(borrowed);
    greet("rust");
}

Shadowing

fn main() {
    let x = 5;
    let x = x + 1;
    let x = x.to_string();

    println!("{x}");
}

Tuple type

fn main() {
    let pair: (i32, i32) = (19, 34);
    println!("{} {}", pair.0, pair.1);
}

Structs, Enums, and Pattern Matching

Struct and method

#![allow(unused)]
fn main() {
struct User {
    name: String,
    active: bool,
}

impl User {
    fn new(name: impl Into<String>) -> Self {
        Self {
            name: name.into(),
            active: true,
        }
    }

    fn deactivate(&mut self) {
        self.active = false;
    }
}
}

Enum and match

#![allow(unused)]
fn main() {
enum State {
    Ready,
    Running(u32),
    Failed(String),
}

fn describe(state: State) -> String {
    match state {
        State::Ready => "ready".to_string(),
        State::Running(pid) => format!("running: {pid}"),
        State::Failed(msg) => format!("failed: {msg}"),
    }
}
}

Option

fn first_char(s: &str) -> Option<char> {
    s.chars().next()
}

fn main() {
    match first_char("rust") {
        Some(c) => println!("{c}"),
        None => println!("empty"),
    }
}

Result and ?

#![allow(unused)]
fn main() {
use std::num::ParseIntError;

fn parse_port(input: &str) -> Result<u16, ParseIntError> {
    let port = input.parse::<u16>()?;
    Ok(port)
}
}

if let

fn main() {
    let maybe = Some(10);

    if let Some(v) = maybe {
        println!("{v}");
    }
}

Pattern binding in match

fn main() {
    let maybe = Some(42);

    match maybe {
        Some(x) => println!("value = {x}"),
        None => println!("nothing"),
    }
}

Exhaustive match

fn main() {
    let x = 24;

    match x {
        23 => println!("twenty-three"),
        24 => println!("twenty-four"),
        _ => println!("something else"),
    }
}

Struct pattern

struct User {
    name: String,
    active: bool,
    admin: bool,
}

fn main() {
    let user = User {
        name: "Bobby".to_string(),
        active: true,
        admin: false,
    };

    match user {
        User { admin: true, .. } => println!("admin"),
        User { active: true, .. } => println!("active"),
        User { .. } => println!("some user"),
    }
}

Traits and Generics

Trait implementation

#![allow(unused)]
fn main() {
trait Describe {
    fn describe(&self) -> String;
}

struct Job {
    id: u64,
}

impl Describe for Job {
    fn describe(&self) -> String {
        format!("job: {}", self.id)
    }
}
}

Generic with trait bound

#![allow(unused)]
fn main() {
fn print_twice<T: std::fmt::Display>(value: T) {
    println!("{value}");
    println!("{value}");
}
}

impl Trait

#![allow(unused)]
fn main() {
fn make_iter() -> impl Iterator<Item = i32> {
    vec![1, 2, 3].into_iter()
}
}

dyn Trait

#![allow(unused)]
fn main() {
fn show(x: &dyn std::fmt::Display) {
    println!("{x}");
}
}

Lifetime example

#![allow(unused)]
fn main() {
fn longest<'a>(a: &'a str, b: &'a str) -> &'a str {
    if a.len() >= b.len() { a } else { b }
}
}

Collections and Iterators

Vec

fn main() {
    let mut nums = vec![1, 2, 3];
    nums.push(4);

    for n in &nums {
        println!("{n}");
    }
}

HashMap

use std::collections::HashMap;

fn main() {
    let mut counts = HashMap::new();
    counts.insert("rust", 1);
    counts.entry("tokio").or_insert(1);
}

Iterator pipeline

fn main() {
    let nums = vec![1, 2, 3, 4, 5, 6];

    let evens: Vec<i32> = nums
        .into_iter()
        .filter(|n| n % 2 == 0)
        .map(|n| n * 10)
        .collect();

    println!("{evens:?}");
}

Concurrency

Channel

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

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

    thread::spawn(move || {
        tx.send(String::from("hello")).unwrap();
    });

    println!("{}", rx.recv().unwrap());
}

Arc<Mutex<T>>

use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    let count = Arc::new(Mutex::new(0));
    let mut handles = Vec::new();

    for _ in 0..4 {
        let count = Arc::clone(&count);
        handles.push(thread::spawn(move || {
            let mut guard = count.lock().unwrap();
            *guard += 1;
        }));
    }

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

    println!("{}", *count.lock().unwrap());
}

Async

Basic async function

#![allow(unused)]
fn main() {
async fn fetch_value() -> u32 {
    42
}
}

tokio::main

#[tokio::main]
async fn main() {
    let value = fetch_value().await;
    println!("{value}");
}

tokio::spawn

#[tokio::main]
async fn main() {
    let handle = tokio::spawn(async { 5 + 5 });
    let result = handle.await.unwrap();
    println!("{result}");
}

tokio::select!

use tokio::time::{sleep, Duration};

#[tokio::main]
async fn main() {
    tokio::select! {
        _ = sleep(Duration::from_millis(10)) => println!("timer 1"),
        _ = sleep(Duration::from_millis(20)) => println!("timer 2"),
    }
}

Blocking hazard

#![allow(unused)]
fn main() {
async fn bad() {
    std::thread::sleep(std::time::Duration::from_millis(50));
}
}

spawn_blocking

#[tokio::main]
async fn main() {
    let result = tokio::task::spawn_blocking(|| {
        std::thread::sleep(std::time::Duration::from_millis(50));
        42
    })
    .await
    .unwrap();

    println!("{result}");
}

Unsafe

Raw pointers

fn main() {
    let mut x = 5;
    let p1 = &x as *const i32;
    let p2 = &mut x as *mut i32;

    unsafe {
        println!("{}", *p1);
        *p2 = 10;
    }
}

Safe wrapper around unsafe

#![allow(unused)]
fn main() {
fn first_byte(slice: &[u8]) -> Option<u8> {
    if slice.is_empty() {
        None
    } else {
        unsafe { Some(*slice.as_ptr()) }
    }
}
}

FFI

unsafe extern "C" {
    fn abs(input: i32) -> i32;
}

fn main() {
    unsafe {
        println!("{}", abs(-3));
    }
}

Drill Method

For each snippet:

  1. Read it once.
  2. Hide it.
  3. Re-type it from memory.
  4. Explain what ownership, borrowing, matching, or trait rule it demonstrates.
  5. Repeat until you can write it without hesitation.