Rust per il Web: Actix Web vs Axum - Confronto e domande da colloquio 2026

Un confronto pratico tra Actix Web 4.14 e Axum 0.8 per lo sviluppo web in Rust nel 2026. Architettura, benchmark TechEmpower Round 23, esperienza di sviluppo e domande da colloquio per posizioni backend Rust.

Confronto tra i framework web Rust Actix Web e Axum per lo sviluppo backend

L'adozione dei framework web Rust ha accelerato notevolmente nel 2026, e due framework dominano i deployment in produzione: Actix Web 4.14 e Axum 0.8. La scelta tra i due influenza tutto, dall'onboarding del team al throughput in produzione, e la domanda emerge frequentemente nei colloqui per posizioni backend Rust.

Framework decisionale rapido

Actix Web 4.14 primeggia nel throughput grezzo (10-15% più richieste al secondo sotto carico elevato). Axum 0.8 offre migliore ergonomia grazie ai trait async nativi, alla composibilità dei middleware Tower e all'integrazione più stretta con Tokio. Per la maggior parte dei team che avviano un nuovo progetto nel 2026, Axum rappresenta la scelta pragmatica, a meno che requisiti di throughput estremi non dettino diversamente.

Differenze architetturali tra Actix Web e Axum

La divergenza architetturale tra questi due framework spiega la maggior parte dei trade-off tra prestazioni ed ergonomia.

Actix Web avvia N runtime Tokio single-threaded, uno per core fisico. I task sono ancorati ai thread senza migrazione di dati cross-thread. Questo elimina l'overhead del work-stealing e il cache-line bouncing, spiegando il vantaggio costante di throughput sotto carico sostenuto.

Axum gira su un singolo runtime Tokio multi-threaded con work-stealing. Il team Tokio ha costruito Axum specificamente per mostrare le capacità del runtime, quindi ogni decisione di design ottimizza per la composibilità con l'ecosistema Tokio più ampio. Gli handler sono semplici funzioni async, e i middleware utilizzano il trait Service di Tower.

actix_hello.rs - Actix Web minimal serverrust
use actix_web::{web, App, HttpServer, HttpResponse};

async fn health() -> HttpResponse {
    HttpResponse::Ok().json(serde_json::json!({ "status": "ok" }))
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
            .route("/health", web::get().to(health))
    })
    .bind("0.0.0.0:8080")?
    .run()
    .await
}
axum_hello.rs - Axum minimal serverrust
use axum::{Router, Json, routing::get};
use serde_json::{json, Value};

async fn health() -> Json<Value> {
    Json(json!({ "status": "ok" }))
}

#[tokio::main]
async fn main() {
    let app = Router::new()
        .route("/health", get(health));

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

Entrambi gli esempi compilano e funzionano, ma le differenze sono già visibili. Actix Web utilizza la propria macro #[actix_web::main] e restituisce std::io::Result. Axum utilizza la macro standard #[tokio::main] e costruisce le route attraverso una struct Router. L'handler Axum restituisce un extractor tipizzato (Json<Value>) invece di costruire manualmente un HttpResponse.

Benchmark delle prestazioni: Actix Web 4.14 vs Axum 0.8

I dati dei benchmark da TechEmpower Round 23 (gennaio 2026) forniscono il confronto più affidabile. Entrambi i framework si posizionano nel tier superiore in tutte le categorie.

MetricaActix Web 4.14Axum 0.8.9
Plaintext (req/s)~7.100.000~6.200.000
Serializzazione JSON (req/s)~1.200.000~1.050.000
Query DB singola (req/s)~190.000~175.000
Utilizzo memoria (hello world)~8 MB~6 MB
Latenza P99 (JSON)1,1 ms1,3 ms

Actix Web mantiene un vantaggio di throughput del 10-15% in tutte le categorie. Axum utilizza leggermente meno memoria grazie al runtime Tokio condiviso. Per contesto, entrambi i framework superano il server HTTP della libreria standard Go di 2-3 volte e Node.js di 5-8 volte su hardware equivalente.

Il divario prestazionale conta per ad serving, pipeline di analytics in tempo reale e gateway di trading ad alta frequenza. Per una tipica API REST che serve 10.000 richieste al secondo, entrambi i framework sono ben oltre il collo di bottiglia, che sarà il database o le chiamate a servizi esterni.

Extractors e gestione delle richieste a confronto

Gli extractors definiscono come i framework parsano le richieste in arrivo. Axum 0.8 ha apportato miglioramenti significativi rimuovendo #[async_trait] a favore dei trait async nativi e introducendo OptionalFromRequestParts per una migliore gestione di Option<T>.

axum_extractors.rs - Axum 0.8 extractor patternrust
use axum::{
    extract::{Path, Query, State, Json},
    routing::get,
    Router,
};
use serde::Deserialize;
use std::sync::Arc;

#[derive(Deserialize)]
struct Pagination {
    page: Option<u32>,    // defaults to None if missing
    per_page: Option<u32>,
}

// State shared across handlers
struct AppState {
    db_pool: sqlx::PgPool,
}

// Axum 0.8: /{id} syntax (replaced /:id)
async fn get_user(
    State(state): State<Arc<AppState>>,
    Path(user_id): Path<i64>,
    Query(pagination): Query<Pagination>,
) -> Json<serde_json::Value> {
    let page = pagination.page.unwrap_or(1);
    // Query database using state.db_pool
    Json(serde_json::json!({
        "user_id": user_id,
        "page": page
    }))
}
actix_extractors.rs - Actix Web extractor patternrust
use actix_web::{web, HttpResponse};
use serde::Deserialize;

#[derive(Deserialize)]
struct Pagination {
    page: Option<u32>,
    per_page: Option<u32>,
}

struct AppState {
    db_pool: sqlx::PgPool,
}

async fn get_user(
    state: web::Data<AppState>,
    path: web::Path<i64>,
    query: web::Query<Pagination>,
) -> HttpResponse {
    let user_id = path.into_inner();
    let page = query.page.unwrap_or(1);
    HttpResponse::Ok().json(serde_json::json!({
        "user_id": user_id,
        "page": page
    }))
}

Gli extractors di Axum usano il tuple destructuring direttamente nei parametri delle funzioni. Actix Web wrappa tutto in web::Path, web::Query, ecc., richiedendo chiamate a .into_inner(). Entrambi gli approcci sono type-safe a compile time, ma l'approccio di Axum risulta più naturale da leggere.

Breaking Change in Axum 0.8

I parametri di path sono passati dalla sintassi /:id a /{id} in Axum 0.8 (via matchit 0.8). Questo si allinea alla sintassi dei path OpenAPI. L'escaping utilizza doppie parentesi graffe: {{ per un letterale {.

Architettura middleware: Tower vs Actix Middleware

La composizione dei middleware è l'area dove la differenza architetturale produce l'impatto pratico maggiore.

Axum utilizza i trait Service e Layer di Tower. Qualsiasi middleware compatibile con Tower funziona con Axum, inclusi rate limiter, tracing, compression e layer di autenticazione costruiti per altri servizi basati su Tower. Questa composibilità si estende oltre HTTP; lo stesso middleware può wrappare servizi gRPC via Tonic.

axum_middleware.rs - Tower middleware compositionrust
use axum::{
    Router, middleware,
    routing::get,
    extract::Request,
    response::Response,
};
use tower_http::{
    cors::CorsLayer,
    compression::CompressionLayer,
    trace::TraceLayer,
};
use std::time::Instant;

// Custom middleware as a plain async function
async fn timing_middleware(
    request: Request,
    next: middleware::Next,
) -> Response {
    let start = Instant::now();
    let response = next.run(request).await;
    let duration = start.elapsed();
    tracing::info!("Request took {:?}", duration);
    response
}

fn build_router() -> Router {
    Router::new()
        .route("/api/data", get(|| async { "ok" }))
        .layer(middleware::from_fn(timing_middleware))
        .layer(CompressionLayer::new())
        .layer(CorsLayer::permissive())
        .layer(TraceLayer::new_for_http())
}

Actix Web utilizza il proprio sistema di middleware con i trait Transform e Service (non quelli di Tower). I middleware dal più ampio ecosistema Tower richiedono adapter o riscritture.

Per i team già investiti nell'ecosistema Tower attraverso Tonic (gRPC) o Hyper, il middleware di Axum rappresenta un vantaggio significativo. Per i team che costruiscono un servizio HTTP standalone, il sistema middleware di Actix Web è ugualmente capace, solo non intercambiabile.

Actix Web 4.14 ha introdotto la validazione dei middleware di route che genera panic se gli handler vengono aggiunti dopo il wrapping del middleware, catturando errori di configurazione all'avvio invece che a runtime.

Pronto a superare i tuoi colloqui su Rust?

Pratica con i nostri simulatori interattivi, flashcards e test tecnici.

Integrazione database con SQLx

Entrambi i framework si integrano bene con SQLx, il toolkit SQL async-first che valida le query a compile time. Il pattern di integrazione differisce leggermente.

shared_db.rs - SQLx with compile-time query validationrust
use sqlx::PgPool;

// This struct works identically with Actix Web and Axum
#[derive(sqlx::FromRow, serde::Serialize)]
struct User {
    id: i64,
    email: String,
    created_at: chrono::NaiveDateTime,
}

// sqlx::query_as! validates against a live DB at compile time
async fn find_user_by_email(
    pool: &PgPool,
    email: &str,
) -> Result<Option<User>, sqlx::Error> {
    sqlx::query_as!(
        User,
        "SELECT id, email, created_at FROM users WHERE email = ",
        email
    )
    .fetch_optional(pool)
    .await
}

Il layer database rimane identico indipendentemente dalla scelta del framework. La macro query_as! di SQLx si connette a un database live a compile time e valida nomi delle colonne, tipi ed esistenza delle tabelle. Un errore di battitura nel nome di una colonna produce un errore di compilazione, non un crash a runtime.

Pattern di gestione degli errori a confronto

La gestione degli errori rivela filosofie di design diverse. Actix Web utilizza implementazioni del trait ResponseError. Axum si basa su IntoResponse combinato con il tipo Result.

axum_errors.rs - Axum error handling with IntoResponserust
use axum::{
    http::StatusCode,
    response::{IntoResponse, Response},
    Json,
};

// Define application-level errors
enum AppError {
    NotFound(String),
    DatabaseError(sqlx::Error),
    ValidationError(String),
}

// Convert errors into HTTP responses
impl IntoResponse for AppError {
    fn into_response(self) -> Response {
        let (status, message) = match self {
            AppError::NotFound(msg) => (
                StatusCode::NOT_FOUND, msg
            ),
            AppError::DatabaseError(_) => (
                StatusCode::INTERNAL_SERVER_ERROR,
                "Internal server error".to_string(),
            ),
            AppError::ValidationError(msg) => (
                StatusCode::BAD_REQUEST, msg
            ),
        };
        (status, Json(serde_json::json!({ "error": message })))
            .into_response()
    }
}

// Handlers return Result<T, AppError>
async fn get_user(
    axum::extract::Path(id): axum::extract::Path<i64>,
) -> Result<Json<serde_json::Value>, AppError> {
    if id <= 0 {
        return Err(AppError::ValidationError(
            "ID must be positive".to_string()
        ));
    }
    Ok(Json(serde_json::json!({ "id": id })))
}

L'approccio di Axum si compone naturalmente con l'operatore ? di Rust e il tipo Result. Actix Web ottiene lo stesso attraverso ResponseError, che richiede l'implementazione sia del trait Display che ResponseError. Entrambi funzionano, ma il pattern di Axum risulta più idiomatico per gli sviluppatori Rust abituati al trait From e alla propagazione degli errori.

Quando scegliere Actix Web

Actix Web rimane la scelta giusta in scenari specifici:

  • Requisiti di throughput massimo: Ad exchange, real-time bidding, pipeline di ingestion analytics dove il 10-15% in più di req/s giustifica il trade-off.
  • Applicazioni WebSocket-intensive: Il supporto WebSocket di Actix Web è collaudato in più deployment di produzione. Il supporto WebSocket di Axum (via axum::extract::ws) funziona bene ma ha una storia di produzione più breve.
  • Codebase Actix Web esistenti: La migrazione da Actix Web 3.x a 4.x è semplice. Riscrivere per Axum offre rendimenti decrescenti per servizi stabili.
  • Familiarità del team: Se il team conosce già Actix Web, cambiare framework per guadagni ergonomici raramente ripaga nel breve termine.

Quando scegliere Axum

Axum si adatta meglio in questi contesti:

  • Nuovi progetti nel 2026: L'allineamento con l'ecosistema Tokio (Tonic, Hyper, Tower) riduce l'attrito nell'integrazione.
  • Servizi misti gRPC e HTTP: I middleware Tower funzionano su entrambi i protocolli senza layer di adattamento.
  • Team nuovi a Rust: Gli extractor type-driven di Axum e i messaggi di errore a compile time offrono una curva di apprendimento più dolce. La sintassi /{id} per i path (allineata con OpenAPI) è immediatamente familiare.
  • Architetture microservices: Il trait Service di Tower consente il riutilizzo dei middleware tra servizi, riducendo il boilerplate.

Domande da colloquio: Actix Web e Axum per posizioni backend Rust

Le domande da colloquio Rust per il backend coprono sempre più la conoscenza dei framework web. Queste domande appaiono nei colloqui per senior backend e systems engineering.

D: Spiegate la differenza architetturale tra i modelli di runtime di Actix Web e Axum.

Actix Web avvia un runtime Tokio single-threaded per ogni core CPU. I task sono ancorati ai thread, eliminando l'overhead del work-stealing. Axum gira su un runtime Tokio multi-threaded condiviso con work-stealing. Il modello di Actix Web riduce la contesa delle cache-line sotto carico elevato, producendo throughput più alto. Il modello di Axum semplifica la gestione dello stato condiviso poiché tutti i task condividono un runtime.

D: Come influisce la rimozione di #[async_trait] in Axum 0.8 sugli extractor personalizzati?

Axum 0.8 sfrutta i trait con impl Trait in posizione di ritorno nativi di Rust (stabilizzati fine 2023). Gli extractor personalizzati che implementano FromRequestParts o FromRequest ora definiscono metodi async direttamente senza l'attributo #[async_trait]. Questo elimina le allocazioni heap da Box<dyn Future> e migliora i tempi di compilazione. Gli extractor esistenti richiedono la rimozione della macro e l'adeguamento delle implementazioni dei trait.

D: Descrivete come i middleware Tower differiscono dai middleware di Actix Web.

Tower definisce un trait generico Service<Request> che è agnostico rispetto al protocollo. Un layer timeout di Tower funziona con HTTP (Axum), gRPC (Tonic) e qualsiasi protocollo personalizzato. I middleware di Actix Web utilizzano i trait Transform e Service specifici del framework. L'impatto pratico: i middleware Axum sono riutilizzabili nell'ecosistema Tower; i middleware Actix Web sono specifici del framework.

D: Come funziona la validazione delle query a compile time di SQLx, e quali sono i trade-off?

La macro query_as! di SQLx si connette a un database PostgreSQL live durante la compilazione. Valida la sintassi SQL, i nomi delle colonne, i tipi e l'esistenza delle tabelle. Il trade-off: il build richiede accesso al database, il che complica le pipeline CI. SQLx fornisce sqlx prepare per generare metadati delle query offline, cachando i risultati della validazione in una directory .sqlx committata nel version control.

D: Quando scegliere Actix Web invece di Axum sarebbe la decisione tecnicamente corretta?

Actix Web è corretto quando il throughput sostenuto è il vincolo primario: ad serving, ingestion di analytics in tempo reale, o gateway di trading ad alta frequenza. Il modello di runtime con thread ancorati elimina l'overhead del work-stealing, producendo il 10-15% in più di req/s sotto carico. Axum è corretto quando la composibilità con l'ecosistema Tokio conta più dei guadagni marginali di throughput, particolarmente in architetture microservices che utilizzano sia HTTP che gRPC.

Errore comune nei colloqui

I candidati spesso affermano che un framework è universalmente migliore. Le risposte forti riconoscono il trade-off: Actix Web ottimizza per il throughput, Axum ottimizza per la composibilità dell'ecosistema. La scelta giusta dipende dai vincoli del sistema, non dalle preferenze personali.

Fonti

Punti chiave per lo sviluppo web Rust nel 2026

  • Actix Web 4.14 offre un throughput superiore del 10-15% grazie al modello di runtime con thread ancorati, rendendolo la scelta giusta per servizi sensibili alla latenza e ad alto throughput
  • Axum 0.8 fornisce migliore ergonomia con trait async nativi, sintassi path /{id} e piena compatibilità con i middleware Tower su HTTP e gRPC
  • Entrambi i framework utilizzano lo stesso layer database (SQLx con validazione a compile time), quindi la scelta non influisce sui pattern di accesso ai dati
  • Per nuovi progetti web Rust nel 2026 senza requisiti di throughput estremi, l'allineamento di Axum con Tokio, Tonic e Tower riduce i costi di manutenzione a lungo termine
  • Le domande da colloquio su questo argomento testano la comprensione dei modelli di runtime, dell'architettura middleware e del ragionamento sui trade-off, non la preferenza per il framework
  • Prepararsi per i colloqui Rust richiede la comprensione delle decisioni architetturali di entrambi i framework, non solo della sintassi delle API

Inizia a praticare!

Metti alla prova le tue conoscenze con i nostri simulatori di colloquio e test tecnici.

Sfida del giorno

Sapresti trovare il bug in Rust?

Uno snippet reale, un bug nascosto, un tentativo al giorno. Senza account per provare.

Anthony Fillion-Maillet

Scritto da

Anthony Fillion-Maillet

Fondatore di SharpSkill

Sviluppatore fullstack da oltre 10 anni. Guida SharpSkill e risponde di tutto ciò che vi viene pubblicato.

Aggiornato il 22 agosto 2026

Tag

#rust
#actix-web
#axum
#web-framework
#backend
#comparison

Condividi

Articoli correlati