Rust
Ownership, borrowing, async, and idiomatic patterns for safe, performant Rust systems.
Core Concepts
Ownership & Borrowing
// Ownership transfer (move)
let s1 = String::from("hello");
let s2 = s1; // s1 no longer valid
// Borrowing (references)
fn len(s: &String) -> usize { s.len() } // immutable borrow
fn append(s: &mut String) { s.push_str(" world") } // mutable borrow
// Rules:
// 1. One mutable reference OR any number of immutable references
// 2. References must always be valid (no dangling)
Lifetimes
// Explicit lifetime annotation
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
// Struct with borrowed data
struct Config<'a> {
name: &'a str,
version: &'a str,
}
// 'static = lives for entire program
fn takes_static(s: &'static str) { }
Option / Result (No Null, No Exceptions)
// Option<T> — value or nothing
fn find_user(id: u64) -> Option<User> { ... }
// Result<T, E> — success or error
fn save_user(user: User) -> Result<(), DbError> { ... }
// Pattern matching
match find_user(42) {
Some(user) => println!("Found: {}", user.name),
None => println!("Not found"),
}
// Combinators
let user = find_user(42).ok_or(NotFound)?;
let name = user.name.clone().unwrap_or_default();
Type System Patterns
Newtype Pattern (Type Safety)
struct UserId(u64);
struct Email(String);
impl Email {
fn new(s: String) -> Result<Self, ValidationError> {
if s.contains('@') { Ok(Self(s)) } else { Err(ValidationError) }
}
}
// Prevents mixing UserId with OrderId, etc.
fn get_user(id: UserId) -> Option<User> { ... }
State Machines with Enums
enum OrderState {
Pending,
Confirmed { confirmed_at: DateTime<Utc> },
Shipped { tracking: String },
Delivered { delivered_at: DateTime<Utc> },
Cancelled { reason: String },
}
impl OrderState {
fn ship(self, tracking: String) -> Result<Self, InvalidTransition> {
match self {
OrderState::Confirmed { .. } => Ok(OrderState::Shipped { tracking }),
_ => Err(InvalidTransition),
}
}
}
Traits as Interfaces
// Domain traits (ports)
#[async_trait]
trait UserRepository: Send + Sync {
async fn find_by_id(&self, id: UserId) -> Result<Option<User>, RepoError>;
async fn save(&self, user: User) -> Result<(), RepoError>;
}
// Multiple implementations
struct PostgresRepo { pool: PgPool }
struct InMemoryRepo { users: DashMap<UserId, User> }
#[async_trait]
impl UserRepository for PostgresRepo { ... }
#[async_trait]
impl UserRepository for InMemoryRepo { ... }
Error Handling
thiserror / anyhow
// Library errors — thiserror
#[derive(Error, Debug)]
pub enum DomainError {
#[error("user not found: {0}")]
NotFound(UserId),
#[error("invalid email: {0}")]
InvalidEmail(String),
#[error(transparent)]
Db(#[from] sqlx::Error),
}
// Application errors — anyhow
use anyhow::{Context, Result};
async fn create_user(cmd: CreateUser) -> Result<User> {
let email = Email::new(cmd.email).context("invalid email")?;
let user = User::new(cmd.name, email);
repo.save(&user).await.context("failed to save user")?;
Ok(user)
}
Error Handling in Async
// Graceful degradation
async fn get_dashboard(user_id: UserId) -> Result<Dashboard> {
let (profile, orders, recommendations) = tokio::join!(
profile_service.get(user_id),
order_service.recent(user_id),
recommendation_service.for_user(user_id)
);
Ok(Dashboard {
profile: profile?,
orders: orders.unwrap_or_default(), // non-critical
recommendations: recommendations.unwrap_or_default(),
})
}
Async Rust
Tokio Runtime
#[tokio::main]
async fn main() -> Result<()> {
// Multi-threaded runtime by default
let server = HttpServer::new(|| App::new().configure(routes))
.bind("0.0.0.0:8080")?
.run();
server.await?;
Ok(())
}
Structured Concurrency (tokio::task)
use tokio::task::JoinSet;
async fn process_all(items: Vec<Item>) -> Vec<Result<Output>> {
let mut set = JoinSet::new();
for item in items {
set.spawn(async move { process_item(item).await });
}
let mut results = Vec::new();
while let Some(res) = set.join_next().await {
results.push(res.unwrap_or_else(|e| Err(e.into())));
}
results
}
// With semaphore for backpressure
use tokio::sync::Semaphore;
async fn bounded_process(items: Vec<Item>, limit: usize) -> Vec<Result<Output>> {
let sem = Arc::new(Semaphore::new(limit));
let mut set = JoinSet::new();
for item in items {
let permit = sem.clone().acquire_owned().await.unwrap();
set.spawn(async move {
let _permit = permit; // held until end
process_item(item).await
});
}
// ... collect
}
Channels (mpsc, oneshot, watch)
// MPSC — multiple producers, single consumer
let (tx, mut rx) = tokio::sync::mpsc::channel(100);
tokio::spawn(async move {
for item in items {
tx.send(item).await.unwrap();
}
});
while let Some(item) = rx.recv().await {
process(item).await;
}
// oneshot — single value (async/await bridge)
let (tx, rx) = tokio::sync::oneshot::channel();
spawn_blocking(move || { tx.send(heavy_computation()).unwrap(); });
let result = rx.await.unwrap();
Cancellation & Timeouts
use tokio::time::{timeout, Duration};
use tokio_util::sync::CancellationToken;
let token = CancellationToken::new();
let child_token = token.child_token();
tokio::select! {
_ = long_running_task(child_token) => {},
_ = timeout(Duration::from_secs(30), wait_for_signal()) => {
token.cancel(); // propagate cancellation
}
}
Project Structure (Clean Architecture)
.
├── Cargo.toml
├── Cargo.lock
├── src/
│ ├── main.rs # Binary entry
│ ├── lib.rs # Library root
│ ├── domain/
│ │ ├── mod.rs
│ │ ├── entities/
│ │ ├── value_objects/
│ │ ├── services/
│ │ ├── events/
│ │ └── repositories/ # Traits (ports)
│ ├── application/
│ │ ├── commands/
│ │ ├── queries/
│ │ └── dto/
│ ├── infrastructure/
│ │ ├── persistence/ # sqlx, sea-orm, diesel
│ │ ├── http/ # axum, actix-web, poem
│ │ ├── messaging/ # lapin, rdkafka, async-nats
│ │ └── config.rs # figment, config
│ └── platform/ # logging, metrics, tracing
├── tests/ # Integration tests
├── benches/ # Criterion benchmarks
└── migrations/ # sqlx migrate
Tooling Stack (2024+)
| Category | Recommended | Alternatives |
|---|---|---|
| Build | cargo (workspace support) |
— |
| Linting | clippy (built-in) | — |
| Formatting | rustfmt (built-in) |
— |
| Testing | cargo test + proptest |
criterion (bench) |
| Async Runtime | tokio (full) / async-std | glommio (io_uring) |
| Web Framework | axum (modular, tower-based) | actix-web, poem, salvo |
| Database | sqlx (compile-time checked) | sea-orm, diesel, surrealdb |
| Serialization | serde + serde_json | rmp-serde (msgpack) |
| Config | figment / config | envy (env-only) |
| Observability | tracing + opentelemetry | slog, log |
| Error Handling | thiserror (libs) / anyhow (apps) | eyre |
| Validation | validator derive | garde |
| Mocking | mockall (auto) / manual impls | — |
| Vulnerability Scan | cargo-audit / cargo-deny | Trivy |
Cargo.toml (Workspace)
[workspace]
resolver = "2"
members = ["domain", "application", "infrastructure", "api"]
[workspace.dependencies]
tokio = { version = "1.38", features = ["full", "rt-multi-thread"] }
axum = "0.7"
sqlx = { version = "0.7", features = ["runtime-tokio", "postgres", "uuid", "chrono", "json"] }
serde = { version = "1.0", features = ["derive"] }
thiserror = "1.0"
anyhow = "1.0"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] }
opentelemetry = { version = "0.27", features = ["trace"] }
opentelemetry-otlp = { version = "0.22", features = ["grpc"] }
validator = { version = "0.18", features = ["derive"] }
uuid = { version = "1.7", features = ["v4", "serde", "sqlx"] }
chrono = { version = "0.4", features = ["serde", "sqlx"] }
rustfmt.toml
edition = "2021"
max_width = 100
use_small_heuristics = "Max"
imports_granularity = "Module"
group_imports = "StdExternalCrate"
Database (sqlx)
Compile-Time Checked Queries
// migrations/20240101_create_users.sql
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email TEXT NOT NULL UNIQUE,
name TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- src/infrastructure/persistence/user_repo.rs
#[derive(sqlx::FromRow)]
struct UserRow {
id: Uuid,
email: String,
name: String,
created_at: DateTime<Utc>,
}
#[sqlx::query("SELECT id, email, name, created_at FROM users WHERE id = $1")]
async fn find_by_id(pool: &PgPool, id: Uuid) -> Result<Option<UserRow>, sqlx::Error>;
#[sqlx::query(
"INSERT INTO users (id, email, name) VALUES ($1, $2, $3)
RETURNING id, email, name, created_at"
)]
async fn insert(pool: &PgPool, id: Uuid, email: &str, name: &str) -> Result<UserRow, sqlx::Error>;
Repository Implementation
pub struct PostgresUserRepo { pool: PgPool }
#[async_trait]
impl UserRepository for PostgresUserRepo {
async fn find_by_id(&self, id: UserId) -> Result<Option<User>, RepoError> {
let row = find_by_id(&self.pool, id.0).await?;
Ok(row.map(User::from))
}
async fn save(&self, user: User) -> Result<(), RepoError> {
insert(&self.pool, user.id.0, &user.email.0, &user.name).await?;
Ok(())
}
}
HTTP (axum)
use axum::{routing::{get, post}, Router, Json, extract::{Path, State}};
use tower::ServiceBuilder;
use tower_http::trace::TraceLayer;
#[derive(Clone)]
struct AppState { user_service: Arc<dyn UserService> }
async fn get_user(State(state): State<AppState>, Path(id): Path<UserId>) -> Result<Json<User>, AppError> {
let user = state.user_service.get_user(id).await?;
Ok(Json(user))
}
async fn create_user(State(state): State<AppState>, Json(cmd): Json<CreateUserCmd>) -> Result<Json<User>, AppError> {
let user = state.user_service.create_user(cmd).await?;
Ok(Json(user))
}
fn router(state: AppState) -> Router {
Router::new()
.route("/users/:id", get(get_user))
.route("/users", post(create_user))
.layer(ServiceBuilder::new().layer(TraceLayer::new_for_http()))
.with_state(state)
}
Observability (tracing + OpenTelemetry)
use tracing::{info, error, instrument};
use tracing_subscriber::{fmt, EnvFilter};
use opentelemetry_otlp::WithExportConfig;
fn init_tracing() -> Result<()> {
let otlp = opentelemetry_otlp::new_pipeline()
.tracing()
.with_exporter(opentelemetry_otlp::new_exporter().grpc())
.install_batch(opentelemetry::runtime::Tokio)?;
tracing_subscriber::registry()
.with(EnvFilter::from_default_env())
.with(fmt::layer().json())
.with(otlp)
.init();
Ok(())
}
#[instrument(skip(service), fields(user_id = %id))]
async fn get_user(service: &UserService, id: UserId) -> Result<User> {
info!("fetching user");
service.find_by_id(id).await
}
Testing
Unit Tests
#[cfg(test)]
mod tests {
use super::*;
use mockall::predicate::*;
#[tokio::test]
async fn test_create_user() {
let mut mock_repo = MockUserRepository::new();
mock_repo.expect_save()
.with(eq(UserId(Uuid::nil())), always())
.returning(|_, _| Ok(()));
let service = UserService::new(Arc::new(mock_repo));
let result = service.create_user(CreateUserCmd { ... }).await;
assert!(result.is_ok());
}
}
Property-Based Testing (proptest)
use proptest::prelude::*;
proptest! {
#[test]
fn email_roundtrip(s in "[a-z]+@[a-z]+\\.[a-z]+") {
let email = Email::new(s.to_string()).unwrap();
prop_assert_eq!(email.as_str(), s);
}
}
Integration Tests (testcontainers)
#[tokio::test]
async fn test_postgres_repo() {
let container = testcontainers::clients::Cli::default();
let pg = container.run(testcontainers::images::postgres::Postgres::default());
let pool = PgPool::connect(&pg.connection_string()).await.unwrap();
// ... test with real DB
}
Performance
- Zero-cost abstractions — Generics monomorphized; no runtime overhead
- Stack allocation by default —
Box/Arconly when needed - Inline —
#[inline],#[inline(always)]for hot paths - SIMD —
std::simd(nightly) orpacked_simd - Profiling —
cargo flamegraph,perf,instruments - Benchmarking —
criterioncrate
Security
- cargo-audit —
cargo install cargo-audit→cargo audit - cargo-deny — License, bans, sources, advisories policy
- No unsafe — Avoid; if needed, document with
// SAFETY: - Dependencies — Minimize; audit with
cargo tree -d
Related Notes
- Language Agnostic Principles — Universal patterns
- TypeScript JavaScript — JS/TS patterns
- Python — Python patterns
- Go — Go patterns
- Concurrency Patterns — Concurrency deep dive
- Resilience Patterns — Circuit breaker, retry in Rust
- CI CD Best Practices — Pipeline for Rust
- Architectural Styles — Hexagonal in Rust
Sources: The Rust Book, Rust by Example, Effective Rust, Jon Gjengset (YouTube), Rust API Guidelines, Tokio Docs, Axum Docs, sqlx Docs, OpenTelemetry Rust, clippy, rustfmt, cargo-audit, proptest, criterion, mockall