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::
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Discussion
Loading…