正在加载,请稍候…

Rust Async Programming with Tokio: Building High-Performance Web Services

Build high-performance Rust web services using async/await and Tokio. Covers the Future trait, Tokio runtime, spawning tasks, shared state, the Axum web framework, and performance tuning for production systems.

Rust Async Programming with Tokio: Building High-Performance Web Services

Rust's async ecosystem has matured significantly, with Tokio as the de facto standard runtime for production services. Understanding async Rust from the ground up—the Future trait, the executor model, and practical patterns—is essential for building high-performance networked applications.

Understanding the Future Trait

At the core of Rust's async model is the Future trait:

pub trait Future {
    type Output;
    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>;
}

pub enum Poll<T> {
    Ready(T),
    Pending,
}

When you write async fn, the compiler transforms it into a state machine implementing Future. The executor calls poll until it returns Ready.

Setting Up Tokio

Add Tokio to your Cargo.toml:

[dependencies]
tokio = { version = "1", features = ["full"] }
axum = "0.7"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
tower = "0.4"
tracing = "0.1"
tracing-subscriber = "0.3"

The minimal Tokio runtime:

#[tokio::main]
async fn main() {
    println!("Running on Tokio!");
    let result = fetch_data("https://api.example.com").await;
    println!("{:?}", result);
}

async fn fetch_data(url: &str) -> Result<String, reqwest::Error> {
    reqwest::get(url).await?.text().await
}

Spawning Tasks

tokio::spawn creates independent tasks that run concurrently:

use tokio::task::JoinHandle;

async fn concurrent_tasks() {
    let handle1: JoinHandle<i32> = tokio::spawn(async {
        tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
        42
    });

    let handle2: JoinHandle<i32> = tokio::spawn(async {
        tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
        7
    });

    let (r1, r2) = tokio::join!(handle1, handle2);
    println!("{} {}", r1.unwrap(), r2.unwrap());
}

async fn try_concurrent() -> Result<(), Box<dyn std::error::Error>> {
    let (r1, r2) = tokio::try_join!(
        fetch_data("https://api1.example.com"),
        fetch_data("https://api2.example.com"),
    )?;
    println!("{} {}", r1, r2);
    Ok(())
}

Shared State in Async Code

Sharing state between async tasks requires careful handling:

use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::{Mutex, RwLock};

type SharedMap = Arc<Mutex<HashMap<String, String>>>;

async fn write_to_map(map: SharedMap, key: String, value: String) {
    let mut guard = map.lock().await;
    guard.insert(key, value);
}

async fn read_from_map(map: SharedMap, key: &str) -> Option<String> {
    let guard = map.lock().await;
    guard.get(key).cloned()
}

type SharedCache = Arc<RwLock<HashMap<String, Vec<u8>>>>;

async fn cache_put(cache: SharedCache, key: String, data: Vec<u8>) {
    let mut w = cache.write().await;
    w.insert(key, data);
}

async fn cache_get(cache: SharedCache, key: &str) -> Option<Vec<u8>> {
    let r = cache.read().await;
    r.get(key).cloned()
}

Building an Axum Web Service

Axum is the leading Rust web framework built on Tokio and Tower:

use axum::{
    extract::{Path, State},
    http::StatusCode,
    response::Json,
    routing::{get, post},
    Router,
};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;

#[derive(Debug, Clone, Serialize, Deserialize)]
struct User {
    id: u64,
    name: String,
    email: String,
}

type UserStore = Arc<RwLock<HashMap<u64, User>>>;

async fn get_user(
    Path(id): Path<u64>,
    State(store): State<UserStore>,
) -> Result<Json<User>, StatusCode> {
    let users = store.read().await;
    users.get(&id)
        .cloned()
        .map(Json)
        .ok_or(StatusCode::NOT_FOUND)
}

#[derive(Deserialize)]
struct CreateUser {
    name: String,
    email: String,
}

async fn create_user(
    State(store): State<UserStore>,
    Json(payload): Json<CreateUser>,
) -> (StatusCode, Json<User>) {
    let mut users = store.write().await;
    let id = users.len() as u64 + 1;
    let user = User { id, name: payload.name, email: payload.email };
    users.insert(id, user.clone());
    (StatusCode::CREATED, Json(user))
}

#[tokio::main]
async fn main() {
    let store: UserStore = Arc::new(RwLock::new(HashMap::new()));

    let app = Router::new()
        .route("/users", get(list_users).post(create_user))
        .route("/users/:id", get(get_user))
        .with_state(store);

    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}

Middleware with Tower

use axum::middleware::{self, Next};
use axum::extract::Request;
use axum::response::Response;
use std::time::Instant;

async fn timing_middleware(req: Request, next: Next) -> Response {
    let start = Instant::now();
    let path = req.uri().path().to_string();
    let method = req.method().clone();
    let response = next.run(req).await;
    let duration = start.elapsed();
    println!("{} {} took {}ms", method, path, duration.as_millis());
    response
}

async fn auth_middleware(req: Request, next: Next) -> Result<Response, StatusCode> {
    let auth_header = req.headers()
        .get("authorization")
        .and_then(|v| v.to_str().ok());

    match auth_header {
        Some(token) if token.starts_with("Bearer ") => Ok(next.run(req).await),
        _ => Err(StatusCode::UNAUTHORIZED),
    }
}

Performance Tuning

Tokio Runtime Configuration

#[tokio::main(flavor = "multi_thread", worker_threads = 4)]
async fn main() {
    // Use 4 worker threads
}

fn build_runtime() -> tokio::runtime::Runtime {
    tokio::runtime::Builder::new_multi_thread()
        .worker_threads(num_cpus::get())
        .enable_all()
        .thread_stack_size(3 * 1024 * 1024)
        .build()
        .unwrap()
}

Avoiding Blocking in Async Context

use tokio::task;

// WRONG: blocking I/O blocks the Tokio thread
async fn bad_file_read(path: &str) -> String {
    std::fs::read_to_string(path).unwrap() // blocks!
}

// CORRECT: use tokio's async I/O
async fn good_file_read(path: &str) -> tokio::io::Result<String> {
    tokio::fs::read_to_string(path).await
}

// For CPU-heavy work: spawn_blocking
async fn cpu_heavy_work(data: Vec<u8>) -> Vec<u8> {
    task::spawn_blocking(move || {
        data.iter().map(|b| b.wrapping_add(1)).collect()
    })
    .await
    .unwrap()
}

Error Handling Best Practices

use thiserror::Error;

#[derive(Error, Debug)]
enum AppError {
    #[error("User not found: {id}")]
    NotFound { id: u64 },
    #[error("Database error: {0}")]
    Database(#[from] sqlx::Error),
    #[error("Validation error: {message}")]
    Validation { message: String },
}

impl axum::response::IntoResponse for AppError {
    fn into_response(self) -> axum::response::Response {
        let (status, message) = match self {
            AppError::NotFound { id } => (
                StatusCode::NOT_FOUND,
                format!("User {} not found", id),
            ),
            AppError::Database(e) => (
                StatusCode::INTERNAL_SERVER_ERROR,
                format!("Database error: {}", e),
            ),
            AppError::Validation { message } => (
                StatusCode::BAD_REQUEST,
                message,
            ),
        };
        (status, Json(serde_json::json!({"error": message}))).into_response()
    }
}

Rust's async ecosystem in 2026 offers excellent performance rivaling C++, with the memory safety guarantees that make production systems reliable. Tokio and Axum provide the foundation for services handling millions of requests per second with predictable latency.