Rust in web and systems

The web and systems half of the Rust ecosystem is mature and stable. If you have written async server code in any language, the Rust version reads as you would expect, but with stronger guarantees about lifetime, concurrency, and error handling. The crates worth knowing are few.

The async runtime

CrateRole
tokioThe async runtime. Almost everything in production async Rust uses it.
futuresCombinators and utilities for Future and Stream.
async-std / smolAlternatives. Smaller communities. Almost never the right choice for new projects.

Tokio's pieces you will see constantly:

  • tokio::spawn for new tasks.
  • tokio::sync::{mpsc, oneshot, broadcast, watch} for channels.
  • tokio::sync::{Mutex, RwLock} for async-aware locks (different from std::sync).
  • tokio::time::{sleep, timeout, interval} for scheduling.
  • tokio::task::spawn_blocking for CPU-bound work.

HTTP servers

CrateRole
hyperThe HTTP implementation. Low level. Most servers build on top.
axumThe current default. Ergonomic, type-safe, Tokio-native.
actix-webMature alternative. Different design philosophy.
tonicgRPC. What Sail uses for Spark Connect.

For new projects, the default is Axum for HTTP REST/JSON and Tonic for gRPC. Both compose with the rest of the Tokio ecosystem via the tower::Service trait.

A minimal Axum server gives you a sense of the shape:

example: minimal Axum
use axum::{routing::get, Json, Router};
use serde::Serialize;
 
#[derive(Serialize)]
struct Health { ok: bool }
 
#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let app = Router::new()
        .route("/health", get(|| async { Json(Health { ok: true }) }));
 
    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await?;
    axum::serve(listener, app).await?;
    Ok(())
}

Three things to notice: #[tokio::main] sets up the runtime, handlers are plain async closures, serde::Serialize derives JSON output. There is no IoC container, no annotation magic, no surprise.

HTTP clients

CrateRole
reqwestThe default ergonomic client. Built on hyper.
ureqBlocking, smaller, fewer features. Good for CLIs.

For most projects, reqwest with tokio is the right answer. JSON via .json::<T>().await?, TLS via rustls (preferred over OpenSSL).

TLS

CrateRole
rustlsPure-Rust TLS implementation. Strongly preferred over OpenSSL bindings.
tokio-rustlsThe Tokio adapter.
webpki-rootsBundled trust anchors.

Reach for rustls first. The OpenSSL binding (openssl-sys) has historically been a source of build pain and CVEs.

Serialization

CrateRole
serdeThe framework. Everything else builds on top.
serde_jsonJSON. The most-used serde format.
prostProtobuf. What Sail and most gRPC code uses.
bincode, postcard, bson, toml, serde_yaml, csvThe other formats. Pick by need.

Serde is the part of the Rust ecosystem that feels like magic. #[derive(Serialize, Deserialize)] on a struct produces correct, fast code for any format that implements the trait.

CLI

CrateRole
clapThe argument parser. Derive-based or builder-based.
indicatifProgress bars.
crosstermTerminal control (colors, cursor).
ratatuiTUI applications.

For any CLI, start with clap derive macros:

example: clap derive
use clap::Parser;
 
#[derive(Parser)]
struct Cli {
    /// Input file path.
    #[arg(short, long)]
    input: std::path::PathBuf,
    /// Verbose mode.
    #[arg(short, long)]
    verbose: bool,
}
 
fn main() -> anyhow::Result<()> {
    let cli = Cli::parse();
    // ... cli.input, cli.verbose
    Ok(())
}

Database access

CrateRole
sqlxCompile-time-checked SQL. Postgres, MySQL, SQLite.
sea-ormHigher-level ORM.
rusqliteSQLite bindings, sync.
dieselMature ORM with a different design philosophy.

Most teams pick sqlx for new projects: typed queries, async, multiple backends, well-maintained.

Embedded and edge

CrateRole
embassyAsync embedded Rust framework.
no_std ecosystemCrates that compile without the standard library. For microcontrollers.
wasm-bindgen, wasmer-rsWebAssembly.

Most readers will not write embedded Rust. But knowing the ecosystem exists matters: if an agent suggests Rust for an edge device or a Wasm module, the path is well-paved.

What to look for as an orchestrator

When an agent writes web/systems Rust:

PatternWatch for
unwrap_or on parsed JSONHides invalid input. Use ? with a real error.
tokio::spawn without JoinHandleFire-and-forget loses errors.
reqwest without timeoutDefault has no request timeout. Always set one.
serde::Deserialize on user input without boundsMaximum sizes, allowed values, sanitization.
rustls vs opensslPrefer rustls. Push back on OpenSSL bindings.
TLS verification disableddanger_accept_invalid_certs(true) is a red flag outside tests.

The ecosystem is wide but the surface you need for any one task is small. For most projects, tokio + axum + reqwest + sqlx + serde + clap + anyhow + thiserror + tracing is the entire dependency list. Lean on that.