rust-systems
Production Rust systems programming. Ownership, borrowing, lifetimes, traits, async with Tokio, error handling with thiserror/anyhow, Axum web framework, SQLx database, and essential crate selections.
Installation
npx clawhub@latest install rust-systemsView the full skill documentation and source below.
Documentation
Rust Systems Programming
Ownership Mental Model
Every value has ONE owner at a time.
Owner goes out of scope → value is dropped (freed).
Transfer ownership → move (original can't be used).
Borrow → reference (temporary access, no ownership).
Ownership and Borrowing
fn main() {
// Move semantics
let s1 = String::from("hello");
let s2 = s1; // s1 moved to s2
// println!("{}", s1); // ERROR: s1 moved
// Clone for explicit copy
let s3 = s2.clone();
println!("{} {}", s2, s3); // Both valid
// Copy types (stack-allocated primitives are copied, not moved)
let x = 42i32;
let y = x;
println!("{} {}", x, y); // Both valid (i32 is Copy)
// Borrowing — shared reference (&T)
let s = String::from("hello");
let len = calculate_length(&s); // Borrow s
println!("{} has {} chars", s, len); // s still valid
// Mutable borrow (&mut T) — ONE at a time, exclusive
let mut s = String::from("hello");
change(&mut s);
}
fn calculate_length(s: &String) -> usize {
s.len() // Only reads, doesn't own
}
fn change(s: &mut String) {
s.push_str(", world");
}
Error Handling
// Use thiserror for library errors (derived traits)
use thiserror::Error;
#[derive(Debug, Error)]
pub enum AppError {
#[error("Database error: {0}")]
Database(#[from] sqlx::Error),
#[error("Not found: {resource} with id {id}")]
NotFound { resource: String, id: String },
#[error("Invalid input: {0}")]
Validation(String),
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
}
// Use anyhow for application code (ergonomic, any error type)
use anyhow::{Context, Result};
fn load_config(path: &str) -> Result<Config> {
let content = std::fs::read_to_string(path)
.with_context(|| format!("Failed to read config file: {}", path))?;
let config: Config = serde_json::from_str(&content)
.context("Failed to parse config JSON")?;
Ok(config)
}
// Pattern matching on Results
match load_config("config.json") {
Ok(config) => println!("Loaded: {:?}", config),
Err(e) => {
eprintln!("Error: {:#}", e); // #: pretty-print with chain
std::process::exit(1);
}
}
// ? operator — propagate errors
fn get_user(id: u64) -> Result<User, AppError> {
let conn = db::connect()?; // Propagates DB error
let user = conn.query_user(id)?; // Propagates DB error
user.ok_or_else(|| AppError::NotFound {
resource: "user".to_string(),
id: id.to_string(),
})
}
Traits (Rust's Interfaces)
// Define a trait
trait Summarize {
fn summary(&self) -> String;
// Default implementation
fn brief(&self) -> String {
format!("{}...", &self.summary()[..50.min(self.summary().len())])
}
}
// Implement for a struct
struct Article {
title: String,
content: String,
}
impl Summarize for Article {
fn summary(&self) -> String {
format!("{}: {}", self.title, &self.content[..200.min(self.content.len())])
}
}
// Trait objects (dynamic dispatch)
fn notify(item: &dyn Summarize) {
println!("Breaking: {}", item.summary());
}
// Generic with trait bounds (static dispatch — zero cost)
fn notify_generic<T: Summarize>(item: &T) {
println!("Breaking: {}", item.summary());
}
// Multiple bounds
fn notify_display<T: Summarize + std::fmt::Display>(item: &T) {
println!("{}: {}", item, item.summary());
}
// Common built-in traits to implement
impl Clone for MyStruct { ... } // .clone()
impl Debug for MyStruct { ... } // {:?} formatting
impl Display for MyStruct { ... } // {} formatting
impl From<X> for Y { ... } // Conversion (enables ?)
impl Into<Y> for X { ... } // Auto-derived from From
impl Iterator for MyIter { ... } // for loops, .map(), .filter()
Async with Tokio
use tokio::net::TcpListener;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let listener = TcpListener::bind("0.0.0.0:8080").await?;
loop {
let (mut socket, addr) = listener.accept().await?;
// Spawn concurrent task per connection
tokio::spawn(async move {
let mut buf = vec![0; 1024];
loop {
match socket.read(&mut buf).await {
Ok(0) => return, // Connection closed
Ok(n) => {
if let Err(e) = socket.write_all(&buf[..n]).await {
eprintln!("Write error: {}", e);
return;
}
}
Err(e) => {
eprintln!("Read error from {}: {}", addr, e);
return;
}
}
}
});
}
}
// Concurrent futures
use tokio::join;
use tokio::time::{sleep, Duration};
async fn fetch_all() -> (String, String) {
// Run both concurrently (not sequentially)
let (a, b) = join!(fetch_users(), fetch_orders());
(a, b)
}
// Timeout
use tokio::time::timeout;
async fn with_timeout<T>(fut: impl Future<Output = T>) -> Option<T> {
timeout(Duration::from_secs(5), fut).await.ok()
}
Common Patterns
// Builder pattern
#[derive(Debug)]
struct Config {
host: String,
port: u16,
max_connections: usize,
}
#[derive(Default)]
struct ConfigBuilder {
host: Option<String>,
port: Option<u16>,
max_connections: Option<usize>,
}
impl ConfigBuilder {
pub fn host(mut self, host: impl Into<String>) -> Self {
self.host = Some(host.into());
self
}
pub fn port(mut self, port: u16) -> Self {
self.port = Some(port);
self
}
pub fn build(self) -> Result<Config, String> {
Ok(Config {
host: self.host.ok_or("host is required")?,
port: self.port.unwrap_or(8080),
max_connections: self.max_connections.unwrap_or(100),
})
}
}
let config = ConfigBuilder::default()
.host("localhost")
.port(3000)
.build()?;
// Newtype pattern — type-safe wrappers
struct UserId(String);
struct OrderId(String);
// Can't accidentally pass UserId where OrderId is expected
// Type state pattern — encode state in types
struct Locked;
struct Unlocked;
struct Mutex<State> {
data: String,
_state: std::marker::PhantomData<State>,
}
impl Mutex<Locked> {
fn unlock(self, key: &str) -> Option<Mutex<Unlocked>> {
if self.verify(key) { Some(Mutex { data: self.data, _state: std::marker::PhantomData }) }
else { None }
}
}
impl Mutex<Unlocked> {
fn read(&self) -> &str { &self.data }
fn lock(self) -> Mutex<Locked> {
Mutex { data: self.data, _state: std::marker::PhantomData }
}
}
Essential Crates
[dependencies]
# Async runtime
tokio = { version = "1", features = ["full"] }
# Web framework
axum = "0.7"
tower = "0.4"
tower-http = { version = "0.5", features = ["trace", "cors"] }
# HTTP client
reqwest = { version = "0.12", features = ["json", "rustls-tls"] }
# Serialization
serde = { version = "1", features = ["derive"] }
serde_json = "1"
# Database
sqlx = { version = "0.8", features = ["postgres", "runtime-tokio-rustls", "macros"] }
# Error handling
anyhow = "1"
thiserror = "1"
# Logging/tracing
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
# CLI
clap = { version = "4", features = ["derive"] }
# Configuration
config = "0.14"
dotenvy = "0.15"
[dev-dependencies]
tokio-test = "0.4"
mockall = "0.12"
// Axum web framework example
use axum::{routing::get, Router, extract::State, Json};
#[derive(Clone)]
struct AppState {
db: PgPool,
}
async fn get_users(State(state): State<AppState>) -> Json<Vec<User>> {
let users = sqlx::query_as!(User, "SELECT * FROM users LIMIT 100")
.fetch_all(&state.db)
.await
.unwrap();
Json(users)
}
#[tokio::main]
async fn main() {
let pool = PgPoolOptions::new()
.max_connections(25)
.connect(&std::env::var("DATABASE_URL").unwrap())
.await
.unwrap();
let state = AppState { db: pool };
let app = Router::new()
.route("/users", get(get_users))
.with_state(state);
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, app).await.unwrap();
}