# Rust Ecosystem Reference ## Table of Contents 1. [serde Advanced](#1-serde-advanced) 2. [clap](#2-clap) 3. [reqwest](#3-reqwest) 4. [sqlx](#4-sqlx) 5. [axum](#5-axum) 6. [tracing](#6-tracing) 7. [rayon](#7-rayon) 8. [itertools](#8-itertools) 9. [Cow](#9-cow) --- ## 1. serde Advanced ### Use Custom Serialization with `serialize_with` / `deserialize_with` ```rust use serde::{Deserialize, Serialize}; use chrono::{DateTime, Utc}; #[derive(Serialize, Deserialize)] pub struct Event { pub name: String, #[serde(with = "chrono::serde::ts_seconds")] pub occurred_at: DateTime, #[serde( serialize_with = "serialize_uppercase", deserialize_with = "deserialize_uppercase" )] pub code: String, } fn serialize_uppercase(value: &str, s: S) -> Result where S: serde::Serializer, { s.serialize_str(&value.to_uppercase()) } fn deserialize_uppercase<'de, D>(d: D) -> Result where D: serde::Deserializer<'de>, { let raw = String::deserialize(d)?; Ok(raw.to_uppercase()) } ``` ### Use `#[serde(with)]` for Custom Module ```rust mod as_base64 { use base64::{engine::general_purpose, Engine}; use serde::{Deserialize, Deserializer, Serializer}; pub fn serialize(bytes: &[u8], s: S) -> Result where S: Serializer, { s.serialize_str(&general_purpose::STANDARD.encode(bytes)) } pub fn deserialize<'de, D>(d: D) -> Result, D::Error> where D: Deserializer<'de>, { let s = String::deserialize(d)?; general_purpose::STANDARD .decode(&s) .map_err(serde::de::Error::custom) } } #[derive(Serialize, Deserialize)] pub struct Secret { #[serde(with = "as_base64")] pub key: Vec, } ``` ### Flatten Nested Structs ```rust #[derive(Serialize, Deserialize)] pub struct Metadata { pub created_by: String, pub version: u32, } #[derive(Serialize, Deserialize)] pub struct Record { pub id: u64, pub name: String, #[serde(flatten)] pub meta: Metadata, // Serializes as: { "id": 1, "name": "...", "created_by": "...", "version": 1 } } // Capture unknown fields #[derive(Serialize, Deserialize)] pub struct Flexible { pub known: String, #[serde(flatten)] pub extra: std::collections::HashMap, } ``` ### Tag Enums (Internal, External, Adjacent, Untagged) ```rust // External (default): { "TypeName": { ...fields } } #[derive(Serialize, Deserialize)] pub enum External { Text { content: String }, Number { value: i64 }, } // Internal: { "type": "Text", "content": "..." } #[derive(Serialize, Deserialize)] #[serde(tag = "type")] pub enum Internal { Text { content: String }, Number { value: i64 }, } // Adjacent: { "type": "Text", "data": { "content": "..." } } #[derive(Serialize, Deserialize)] #[serde(tag = "type", content = "data")] pub enum Adjacent { Text { content: String }, Number { value: i64 }, } // Untagged: tries each variant until one succeeds #[derive(Serialize, Deserialize)] #[serde(untagged)] pub enum Untagged { Text { content: String }, Number { value: i64 }, Raw(String), } ``` ### Reject Unknown Fields ```rust #[derive(Deserialize)] #[serde(deny_unknown_fields)] pub struct StrictConfig { pub host: String, pub port: u16, // Any unknown key in JSON causes deserialization to fail } ``` ### Use `#[serde(remote)]` for External Types ```rust // For types you don't own, create a remote definition #[derive(Serialize, Deserialize)] #[serde(remote = "std::time::Duration")] struct DurationDef { secs: u64, nanos: u32, } #[derive(Serialize, Deserialize)] pub struct Config { #[serde(with = "DurationDef")] pub timeout: std::time::Duration, } ``` --- ## 2. clap ### Define CLI with Derive API ```rust use clap::{Args, Parser, Subcommand, ValueEnum}; #[derive(Parser)] #[command(name = "mytool", version, about = "A tool that does things")] pub struct Cli { #[command(subcommand)] pub command: Commands, /// Increase verbosity (-v, -vv, -vvv) #[arg(short, long, action = clap::ArgAction::Count, global = true)] pub verbose: u8, /// Config file path #[arg(long, env = "MYTOOL_CONFIG", default_value = "config.toml", global = true)] pub config: std::path::PathBuf, } #[derive(Subcommand)] pub enum Commands { /// Fetch data from the server Fetch(FetchArgs), /// Push data to the server Push(PushArgs), } #[derive(Args)] pub struct FetchArgs { /// Target URL #[arg(value_parser = parse_url)] pub url: url::Url, /// Output format #[arg(long, value_enum, default_value_t = OutputFormat::Json)] pub format: OutputFormat, /// Optional tags (can be repeated) #[arg(long = "tag", short = 't')] pub tags: Vec, /// Dry run mode #[arg(long, conflicts_with = "output")] pub dry_run: bool, /// Write output to file #[arg(long)] pub output: Option, } #[derive(ValueEnum, Clone)] pub enum OutputFormat { Json, Csv, Pretty, } fn parse_url(s: &str) -> Result { url::Url::parse(s).map_err(|e| e.to_string()) } ``` ### Parse and Dispatch ```rust fn main() { let cli = Cli::parse(); match cli.command { Commands::Fetch(args) => run_fetch(args, cli.verbose), Commands::Push(args) => run_push(args, cli.verbose), } } ``` ### Generate Shell Completions ```rust use clap::CommandFactory; use clap_complete::{generate, Shell}; fn print_completions(shell: Shell) { let mut cmd = Cli::command(); generate(shell, &mut cmd, "mytool", &mut std::io::stdout()); } ``` --- ## 3. reqwest ### Build a Shared Client ```rust use reqwest::{Client, ClientBuilder, header}; use std::time::Duration; fn build_client(base_token: &str) -> reqwest::Result { let mut headers = header::HeaderMap::new(); let auth = header::HeaderValue::from_str(&format!("Bearer {}", base_token)) .expect("Invalid token"); headers.insert(header::AUTHORIZATION, auth); ClientBuilder::new() .timeout(Duration::from_secs(30)) .connect_timeout(Duration::from_secs(5)) .default_headers(headers) .user_agent("myapp/1.0") .build() } ``` ### Send GET / POST / PUT Requests ```rust use serde::{Deserialize, Serialize}; #[derive(Deserialize)] struct ApiResponse { data: Vec } #[derive(Serialize)] struct CreateRequest { name: String, value: u32 } async fn fetch_items(client: &Client, url: &str) -> anyhow::Result> { let resp = client .get(url) .query(&[("limit", "100"), ("page", "1")]) .send() .await? .error_for_status()? .json::() .await?; Ok(resp.data) } async fn create_item(client: &Client, url: &str, name: &str) -> anyhow::Result { let body = CreateRequest { name: name.to_string(), value: 42 }; client .post(url) .json(&body) .send() .await? .error_for_status()? .json::() .await .map_err(Into::into) } ``` ### Upload Multipart Form ```rust use reqwest::multipart; async fn upload_file(client: &Client, url: &str, path: &std::path::Path) -> anyhow::Result<()> { let file_bytes = tokio::fs::read(path).await?; let filename = path.file_name().unwrap().to_string_lossy().into_owned(); let part = multipart::Part::bytes(file_bytes) .file_name(filename) .mime_str("application/octet-stream")?; let form = multipart::Form::new() .text("description", "my upload") .part("file", part); client.post(url).multipart(form).send().await?.error_for_status()?; Ok(()) } ``` ### Stream a Response ```rust use futures_util::StreamExt; async fn stream_download(client: &Client, url: &str) -> anyhow::Result> { let mut stream = client.get(url).send().await?.bytes_stream(); let mut buf = Vec::new(); while let Some(chunk) = stream.next().await { buf.extend_from_slice(&chunk?); } Ok(buf) } ``` ### Retry with Exponential Backoff ```rust use std::time::Duration; async fn get_with_retry(client: &Client, url: &str, max: usize) -> anyhow::Result { let mut delay = Duration::from_millis(200); for attempt in 0..max { match client.get(url).send().await?.error_for_status() { Ok(resp) => return Ok(resp.text().await?), Err(e) if attempt + 1 < max => { tokio::time::sleep(delay).await; delay *= 2; tracing::warn!(attempt, %e, "Retrying request"); } Err(e) => return Err(e.into()), } } unreachable!() } ``` --- ## 4. sqlx ### Set Up a Connection Pool ```toml # Cargo.toml sqlx = { version = "0.7", features = ["postgres", "runtime-tokio-rustls", "macros", "chrono", "uuid"] } ``` ```rust use sqlx::PgPool; pub async fn connect(database_url: &str) -> sqlx::Result { PgPool::connect(database_url).await } // Or with options use sqlx::postgres::PgPoolOptions; pub async fn connect_pool(database_url: &str) -> sqlx::Result { PgPoolOptions::new() .max_connections(20) .acquire_timeout(std::time::Duration::from_secs(5)) .connect(database_url) .await } ``` ### Write Compile-Time Checked Queries ```rust // DATABASE_URL must be set at compile time // sqlx::query! checks SQL against live schema pub async fn get_user(pool: &PgPool, id: i64) -> sqlx::Result> { sqlx::query_as!( User, r#"SELECT id, name, email, created_at FROM users WHERE id = $1"#, id ) .fetch_optional(pool) .await } pub async fn list_users(pool: &PgPool, limit: i64) -> sqlx::Result> { sqlx::query_as!( User, r#"SELECT id, name, email, created_at FROM users ORDER BY id LIMIT $1"#, limit ) .fetch_all(pool) .await } ``` ### Derive `FromRow` ```rust use sqlx::FromRow; use chrono::{DateTime, Utc}; use uuid::Uuid; #[derive(Debug, FromRow)] pub struct User { pub id: i64, pub name: String, pub email: String, pub created_at: DateTime, } // Override column name #[derive(Debug, FromRow)] pub struct Post { pub id: Uuid, #[sqlx(rename = "body_text")] pub body: String, // Skip a column that won't be in every query #[sqlx(skip)] pub computed: Option, } ``` ### Use Transactions ```rust pub async fn transfer_funds( pool: &PgPool, from: i64, to: i64, amount: i64, ) -> sqlx::Result<()> { let mut tx = pool.begin().await?; sqlx::query!("UPDATE accounts SET balance = balance - $1 WHERE id = $2", amount, from) .execute(&mut *tx) .await?; sqlx::query!("UPDATE accounts SET balance = balance + $1 WHERE id = $2", amount, to) .execute(&mut *tx) .await?; tx.commit().await } ``` ### Map JSON Columns ```rust use serde::{Deserialize, Serialize}; use sqlx::types::Json; #[derive(Debug, Serialize, Deserialize)] pub struct Settings { pub theme: String, pub notifications: bool, } #[derive(Debug, FromRow)] pub struct UserWithSettings { pub id: i64, pub name: String, pub settings: Json, // maps JSON column to typed struct } pub async fn get_settings(pool: &PgPool, id: i64) -> sqlx::Result { let row = sqlx::query_as!( UserWithSettings, r#"SELECT id, name, settings AS "settings: Json" FROM users WHERE id = $1"#, id ) .fetch_one(pool) .await?; Ok(row.settings.0) } ``` ### Run Migrations ```rust // Migrations live in ./migrations/*.sql, named 0001_create_users.sql etc. pub async fn run_migrations(pool: &PgPool) -> sqlx::Result<()> { sqlx::migrate!("./migrations").run(pool).await } ``` --- ## 5. axum ### Define a Router with State ```rust use axum::{Router, routing::{get, post}}; use std::sync::Arc; #[derive(Clone)] pub struct AppState { pub pool: sqlx::PgPool, pub config: Arc, } pub fn build_router(state: AppState) -> Router { Router::new() .route("/health", get(health_handler)) .route("/users", get(list_users).post(create_user)) .route("/users/:id", get(get_user).delete(delete_user)) .nest("/admin", admin_routes()) .layer(tower_http::trace::TraceLayer::new_for_http()) .with_state(state) } ``` ### Write Handlers with Extractors ```rust use axum::{ extract::{Path, Query, State, Json}, http::StatusCode, response::IntoResponse, }; use serde::Deserialize; #[derive(Deserialize)] pub struct Pagination { pub page: Option, pub limit: Option, } pub async fn list_users( State(state): State, Query(params): Query, ) -> impl IntoResponse { let limit = params.limit.unwrap_or(20).min(100); let page = params.page.unwrap_or(0); match fetch_users(&state.pool, limit as i64, page as i64).await { Ok(users) => Json(users).into_response(), Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(), } } #[derive(Deserialize)] pub struct CreateUserRequest { pub name: String, pub email: String, } pub async fn create_user( State(state): State, Json(body): Json, ) -> Result<(StatusCode, Json), AppError> { let user = insert_user(&state.pool, &body.name, &body.email).await?; Ok((StatusCode::CREATED, Json(user))) } ``` ### Define a Typed Error Response ```rust use axum::{http::StatusCode, response::{IntoResponse, Response}, Json}; use serde_json::json; pub enum AppError { NotFound(String), Database(sqlx::Error), Validation(String), } impl IntoResponse for AppError { fn into_response(self) -> Response { let (status, message) = match self { AppError::NotFound(msg) => (StatusCode::NOT_FOUND, msg), AppError::Validation(msg) => (StatusCode::UNPROCESSABLE_ENTITY, msg), AppError::Database(e) => { tracing::error!("Database error: {}", e); (StatusCode::INTERNAL_SERVER_ERROR, "Internal error".to_string()) } }; (status, Json(json!({ "error": message }))).into_response() } } impl From for AppError { fn from(e: sqlx::Error) -> Self { AppError::Database(e) } } ``` ### Handle WebSocket Connections ```rust use axum::extract::ws::{Message, WebSocket, WebSocketUpgrade}; pub async fn ws_handler(ws: WebSocketUpgrade) -> impl IntoResponse { ws.on_upgrade(handle_socket) } async fn handle_socket(mut socket: WebSocket) { while let Some(msg) = socket.recv().await { match msg { Ok(Message::Text(text)) => { if socket.send(Message::Text(format!("echo: {text}"))).await.is_err() { break; } } Ok(Message::Close(_)) | Err(_) => break, _ => {} } } } ``` ### Gracefully Shut Down the Server ```rust use tokio::net::TcpListener; pub async fn serve(router: Router) -> anyhow::Result<()> { let listener = TcpListener::bind("0.0.0.0:3000").await?; axum::serve(listener, router) .with_graceful_shutdown(shutdown_signal()) .await?; Ok(()) } async fn shutdown_signal() { tokio::signal::ctrl_c().await.expect("Failed to install Ctrl+C handler"); tracing::info!("Shutdown signal received"); } ``` --- ## 6. tracing ### Set Up a Subscriber ```rust use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter}; pub fn init_tracing() { tracing_subscriber::registry() .with(EnvFilter::try_from_default_env().unwrap_or_else(|_| "info".into())) .with(tracing_subscriber::fmt::layer().with_target(true)) .init(); } // For JSON output (structured logging in prod) pub fn init_json_tracing() { tracing_subscriber::registry() .with(EnvFilter::try_from_default_env().unwrap_or_else(|_| "info".into())) .with(tracing_subscriber::fmt::layer().json()) .init(); } ``` ### Instrument Functions ```rust use tracing::{instrument, info, warn, error, debug, Span}; #[instrument(skip(pool), fields(user_id = %id))] pub async fn get_user_profile(pool: &PgPool, id: i64) -> anyhow::Result { debug!("Fetching user profile"); let profile = sqlx::query_as!(Profile, "SELECT * FROM profiles WHERE user_id = $1", id) .fetch_optional(pool) .await? .ok_or_else(|| anyhow::anyhow!("Profile not found"))?; info!(email = %profile.email, "Profile fetched"); Ok(profile) } ``` ### Create and Enter Spans Manually ```rust use tracing::{info_span, Instrument}; pub async fn process_batch(items: Vec) { for item in items { let span = info_span!("process_item", item_id = %item.id, kind = %item.kind); async move { tracing::info!("Processing item"); // work... tracing::info!("Item done"); } .instrument(span) .await; } } ``` ### Add Structured Fields to Events ```rust use tracing::info; pub fn log_request(method: &str, path: &str, status: u16, latency_ms: u64) { info!( http.method = method, http.path = path, http.status = status, latency_ms = latency_ms, "Request completed" ); } ``` ### Filter by Directive ``` # Environment variable examples RUST_LOG=info RUST_LOG=myapp=debug,sqlx=warn,tower_http=info RUST_LOG=debug,hyper=off ``` --- ## 7. rayon ### Parallelize with `par_iter` ```rust use rayon::prelude::*; fn sum_squares(numbers: &[f64]) -> f64 { numbers.par_iter().map(|x| x * x).sum() } fn find_primes(limit: u64) -> Vec { (2..limit) .into_par_iter() .filter(|&n| is_prime(n)) .collect() } fn transform_records(records: Vec) -> Vec { records.into_par_iter().map(process_record).collect() } ``` ### Build a Custom Thread Pool ```rust use rayon::ThreadPoolBuilder; fn main() { let pool = ThreadPoolBuilder::new() .num_threads(4) .thread_name(|i| format!("worker-{}", i)) .build() .unwrap(); pool.install(|| { (0..1000).into_par_iter().for_each(|i| { println!("Processing {}", i); }); }); } ``` ### Bridge Sequential Iterators ```rust use rayon::iter::ParallelBridge; fn process_lines(reader: impl std::io::BufRead + Send) -> Vec { reader .lines() .par_bridge() // converts Iterator -> ParallelIterator .filter_map(|l| l.ok()) .map(|l| l.trim().to_string()) .collect() } ``` ### Know When to Choose rayon vs tokio | Situation | Use | |-----------|-----| | CPU-bound work (parsing, compression, crypto) | `rayon` | | I/O-bound work (network, disk, database) | `tokio` | | Mix: CPU work inside async | `tokio::task::spawn_blocking` with rayon inside | | Parallel collection transforms | `rayon` | | Concurrent HTTP requests | `tokio` + `FuturesUnordered` | ```rust // Offload rayon work from async context pub async fn heavy_computation(data: Vec) -> Vec { tokio::task::spawn_blocking(move || { data.par_iter().map(|b| b.wrapping_add(1)).collect() }) .await .expect("blocking task panicked") } ``` --- ## 8. itertools ```toml itertools = "0.12" ``` ### Use Useful Combinators ```rust use itertools::Itertools; fn demonstrate_itertools() { let nums = vec![1, 2, 3, 4, 5, 6]; // chunks - fixed-size non-overlapping groups for chunk in &nums.iter().chunks(2) { let v: Vec<_> = chunk.collect(); println!("{:?}", v); // [1,2], [3,4], [5,6] } // tuple_windows - sliding window of tuples let pairs: Vec<_> = nums.iter().tuple_windows::<(_, _)>().collect(); // [(1,2), (2,3), (3,4), (4,5), (5,6)] // group_by - consecutive runs (like Unix uniq) let words = vec!["a", "a", "b", "b", "b", "c"]; for (key, group) in &words.iter().group_by(|w| *w) { println!("{}: {}", key, group.count()); } // join - format with separator (no trailing sep) let s = ["foo", "bar", "baz"].iter().join(", "); // "foo, bar, baz" // sorted_by - sort without mutating let sorted = nums.iter().sorted_by(|a, b| b.cmp(a)).collect_vec(); // dedup - remove consecutive duplicates let deduped: Vec<_> = vec![1, 1, 2, 3, 3, 3, 4].into_iter().dedup().collect(); // [1, 2, 3, 4] // interleave - alternating elements let a = vec![1, 3, 5]; let b = vec![2, 4, 6]; let merged: Vec<_> = a.into_iter().interleave(b.into_iter()).collect(); // [1, 2, 3, 4, 5, 6] // unique - deduplicate (not just consecutive) let unique: Vec<_> = vec![1, 2, 1, 3, 2, 4].into_iter().unique().collect(); // [1, 2, 3, 4] // combinations and permutations let combos: Vec<_> = (0..4).combinations(2).collect(); // [[0,1],[0,2],[0,3],[1,2],[1,3],[2,3]] // partition_map - split into two collections let (evens, odds): (Vec<_>, Vec<_>) = nums.iter().partition_map(|&n| { if n % 2 == 0 { itertools::Either::Left(n) } else { itertools::Either::Right(n) } }); } ``` --- ## 9. Cow ### Understand Clone-on-Write `Cow<'a, B>` is either `Borrowed(&'a B)` or `Owned(B::Owned)`. Derefs to `&B` in both cases. Allocates only when you need to mutate. ### Use Cow in Function Signatures ```rust use std::borrow::Cow; // Accepts both &str and String, returns without allocating if no change needed fn normalize(input: &str) -> Cow { if input.chars().all(|c| c.is_lowercase()) { Cow::Borrowed(input) // zero allocation } else { Cow::Owned(input.to_lowercase()) // allocates only when needed } } // Accept Cow to handle both owned and borrowed callers fn process(name: Cow) { println!("Processing: {}", name); } // Call sites process(Cow::Borrowed("hello")); process(Cow::Owned(String::from("world"))); process(normalize("Mixed")); ``` ### Enable Zero-Copy Parsing ```rust use serde::Deserialize; // serde can borrow from input bytes when possible #[derive(Deserialize)] pub struct Request<'a> { #[serde(borrow)] pub method: Cow<'a, str>, // borrows from JSON bytes if no escaping needed pub id: u64, } ``` ### Build Efficient Builders with Cow ```rust use std::borrow::Cow; pub struct Query<'a> { table: Cow<'a, str>, conditions: Vec>, } impl<'a> Query<'a> { pub fn from_table(table: impl Into>) -> Self { Query { table: table.into(), conditions: vec![] } } pub fn where_clause(mut self, cond: impl Into>) -> Self { self.conditions.push(cond.into()); self } pub fn build(&self) -> String { if self.conditions.is_empty() { format!("SELECT * FROM {}", self.table) } else { format!("SELECT * FROM {} WHERE {}", self.table, self.conditions.iter().join(" AND ")) } } } ```