Error Handling, Traits & Async
Error Handling with Result & Option
// Option<T> — value may or may not exist
fn find_user(id: u32) -> Option<String> {
if id == 1 { Some(String::from("Alice")) }
else { None }
}
match find_user(1) {
Some(name) => println!("Found: {name}"),
None => println!("Not found"),
}
// Shorthand
let name = find_user(1).unwrap(); // panics if None
let name = find_user(1).expect("User not found"); // panic with message
let name = find_user(1).unwrap_or("Unknown".to_string());
let len = find_user(1).map(|n| n.len()); // Option<usize>
let name = find_user(1)?; // propagate None as return
// Result<T, E> — success or error
use std::fs;
use std::io;
fn read_file(path: &str) -> Result<String, io::Error> {
let content = fs::read_to_string(path)?; // ? propagates error
Ok(content)
}
match read_file("config.txt") {
Ok(content) => println!("{content}"),
Err(e) => eprintln!("Error: {e}"),
}
// ? operator — early return on error
fn parse_and_double(s: &str) -> Result<i32, std::num::ParseIntError> {
let n: i32 = s.trim().parse()?; // propagate ParseIntError
Ok(n * 2)
}
// anyhow for application error handling
use anyhow::{Result, Context};
fn run() -> Result<()> {
let data = fs::read_to_string("config.json")
.context("Failed to read config")?;
Ok(())
}Traits
// Trait — like an interface
trait Greet {
fn greet(&self) -> String;
fn farewell(&self) -> String { // default implementation
String::from("Goodbye!")
}
}
struct English;
struct Spanish;
impl Greet for English {
fn greet(&self) -> String { String::from("Hello!") }
}
impl Greet for Spanish {
fn greet(&self) -> String { String::from("¡Hola!") }
fn farewell(&self) -> String { String::from("¡Adiós!") }
}
// Trait as parameter
fn say_hello(lang: &impl Greet) {
println!("{}", lang.greet());
}
// Trait bound syntax (equivalent)
fn say_hello<T: Greet>(lang: &T) {
println!("{}", lang.greet());
}
// Dynamic dispatch (trait object — runtime polymorphism)
fn say_hellos(langs: &[Box<dyn Greet>]) {
for lang in langs {
println!("{}", lang.greet());
}
}
// Derive common traits
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
struct Config { ... }
// Common standard traits
// Display — for println!("{}", val)
// From/Into — type conversion
// Iterator — enables for loops and iterator adapters
// Send/Sync — thread safety markersAsync/Await with Tokio
// Cargo.toml
// tokio = { version = "1", features = ["full"] }
// reqwest = { version = "0.12", features = ["json"] }
use tokio;
use reqwest;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let response = fetch_user(1).await?;
println!("{:?}", response);
Ok(())
}
async fn fetch_user(id: u32) -> anyhow::Result<serde_json::Value> {
let url = format!("https://jsonplaceholder.typicode.com/users/{}", id);
let resp = reqwest::get(&url).await?.json().await?;
Ok(resp)
}
// Parallel tasks
use tokio::task;
let (r1, r2) = tokio::join!(
fetch_user(1),
fetch_user(2),
);
// Spawn concurrent task
let handle = tokio::spawn(async move {
// runs concurrently
heavy_async_work().await
});
let result = handle.await?; // join
// Channels
use tokio::sync::mpsc;
let (tx, mut rx) = mpsc::channel(32);
tokio::spawn(async move { tx.send("hello").await.unwrap(); });
while let Some(msg) = rx.recv().await {
println!("{msg}");
}Keep your own version of these notes — editable, searchable, and organised by your stack.
Start free