iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Exercises

Five Rust drills that exercise ownership, error handling, iterators, and Result.

Five Rust exercises

EXAMPLE
// ============================================================
// Drill 1 — Read a file into a String
// ============================================================
// ANSWER:
use std::fs;
use anyhow::{Context, Result};

fn read_config(path: &str) -> Result<String> {
    fs::read_to_string(path)
        .with_context(|| format!("reading {path}"))
}

// ============================================================
// Drill 2 — Parse newline-separated integers
// ============================================================
// TASK: turn "1\n2\n3\n" into Vec<i64>, skipping blank lines.
//
// ANSWER:
fn parse_ints(s: &str) -> Result<Vec<i64>> {
    s.lines()
     .filter(|l| !l.trim().is_empty())
     .map(|l| l.trim().parse::<i64>().context("parse int"))
     .collect()
}

// ============================================================
// Drill 3 — Aggregate by key
// ============================================================
// TASK: given (key, value) pairs, return HashMap<key, Vec<value>>.
//
// ANSWER:
use std::collections::HashMap;

fn group<K: Eq + std::hash::Hash, V>(pairs: Vec<(K, V)>) -> HashMap<K, Vec<V>> {
    let mut m: HashMap<K, Vec<V>> = HashMap::new();
    for (k, v) in pairs { m.entry(k).or_default().push(v); }
    m
}

// ============================================================
// Drill 4 — Concurrent fetch (async)
// ============================================================
// TASK: fetch N URLs concurrently, return Vec<Result<usize, String>> with the
// body lengths.
//
// ANSWER:
use futures::future::join_all;

async fn fetch_all(urls: Vec<String>) -> Vec<Result<usize, String>> {
    let client = reqwest::Client::new();
    join_all(urls.into_iter().map(|u| {
        let client = client.clone();
        async move {
            let res = client.get(&u).send().await.map_err(|e| e.to_string())?;
            let body = res.bytes().await.map_err(|e| e.to_string())?;
            Ok(body.len())
        }
    })).await
}

// ============================================================
// Drill 5 — Custom error type
// ============================================================
// TASK: model errors for a small loader. Provide variants for IO + Parse +
// NotFound, with #[from] for conversions.
//
// ANSWER:
use thiserror::Error;

#[derive(Debug, Error)]
pub enum LoadError {
    #[error("io error")]
    Io(#[from] std::io::Error),
    #[error("parse error")]
    Parse(#[from] std::num::ParseIntError),
    #[error("not found: {0}")]
    NotFound(String),
}

fn load_age(path: &str) -> std::result::Result<u32, LoadError> {
    let raw = std::fs::read_to_string(path)?;
    let n   = raw.trim().parse::<u32>()?;
    Ok(n)
}

// ============================================================
// Bonus — RAII for a temp directory
// ============================================================
// Use tempfile::TempDir; it removes itself on Drop, automatic cleanup.
// let dir = tempfile::TempDir::new()?;
// std::fs::write(dir.path().join("x.txt"), "hi")?;

// ============================================================
// Scoring
// 5 / 5 -> production-ready Rust
// 3 / 5 -> revisit rust/cheatsheet
// < 3   -> work through the rustlings exercises

Why it matters

`with_context()` + `thiserror` on errors and `Iterator::collect::, _>>()` for fallible mapping are the two patterns that turn Rust error handling from verbose to expressive. Master them and the rest of error handling is mostly choosing between `anyhow::Result` (apps) and a typed enum (libraries).

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
// Fill in: fn add(a: i32, b: i32) -> ____ { a + b }
Try it Yourself »

Discussion

Loading…