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.

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.
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.
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
}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.
| Metrica | Actix Web 4.14 | Axum 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 ms | 1,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>.
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
}))
}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.
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.
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.
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.
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
Servicedi 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.
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
- TechEmpower Framework Benchmarks Round 23 (gennaio 2026): dati dei benchmark per test JSON, plaintext e database
- Axum 0.8.9 Release Notes: selezione subprotocollo WebSocket, versione Rust minima 1.80
- Actix Web 4.14.0 Release (giugno 2026): validazione middleware di route, supporto dual-stack IPv6, h1_write_buffer_size
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.
Sapresti trovare il bug in Rust?
Uno snippet reale, un bug nascosto, un tentativo al giorno. Senza account per provare.

Scritto da
Anthony Fillion-MailletFondatore 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
Condividi
Articoli correlati

Rust e SQLx nel 2026: Query verificate a Compile-Time e Domande da Colloquio
Una guida completa a SQLx 0.9 in Rust con validazione delle query a compile-time, il nuovo sistema di configurazione sqlx.toml e domande da colloquio per sviluppatori Rust.

Imparare Rust nel 2026: Confronto Bootcamp e Risorse per lo Studio Autonomo
Confronto completo tra bootcamp Rust e risorse per lo studio autonomo nel 2026. Scopri i migliori percorsi per padroneggiare Rust, dai corsi strutturati ai materiali gratuiti online.

Rust Lifetimes: Annotazioni, Elision e Domande da Colloquio 2026
Una guida completa ai lifetimes in Rust: dai concetti fondamentali alle annotazioni, le regole di elision e le domande più frequenti nei colloqui tecnici.