Rust na backendzie webowym: Actix Web vs Axum - kompleksowe porównanie i pytania rekrutacyjne 2026
Dogłębna analiza frameworków Actix Web 4.13 i Axum 0.8 dla backendu w Rust. Porównanie architektur runtime, benchmarki wydajności, wzorce ekstraktorów, middleware Tower, integracja z SQLx oraz kluczowe pytania na rozmowy kwalifikacyjne w 2026 roku.

Ekosystem webowy Rust osiągnął w 2026 roku punkt, w którym wybór frameworka backendowego przestał być eksperymentem, a stał się decyzją architektoniczną o wymiernych konsekwencjach produkcyjnych. Dwa frameworki zdominowały krajobraz: Actix Web w wersji 4.13 oraz Axum w wersji 0.8. Każdy z nich reprezentuje odmienną filozofię projektową — od modelu wątków po architekturę middleware — i każdy z nich pojawia się coraz częściej w pytaniach technicznych na rozmowach kwalifikacyjnych dotyczących stanowisk backendowych.
Actix Web 4.13 zapewnia 10-15% wyższą przepustowość dzięki modelowi przypiętych wątków (pinned-thread). Axum 0.8 oferuje natywne async traits, pełną kompatybilność z ekosystemem Tower i prostszą krzywą nauki. W przypadku nowych projektów webowych w Rust rozpoczynanych w 2026 roku Axum stanowi rozsądny wybór domyślny, o ile specyficzne wymagania wydajnościowe nie przemawiają za Actix Web.
Architektura runtime: pinned-thread kontra work-stealing
Fundamentalna różnica między Actix Web a Axum leży w sposobie zarządzania wątkami i planowania zadań asynchronicznych.
Actix Web stosuje model pinned-thread: tworzy N izolowanych, jednowątkowych instancji runtime Tokio, gdzie N odpowiada liczbie fizycznych rdzeni procesora. Każde przychodzące połączenie jest obsługiwane przez dokładnie jeden wątek przez cały cykl życia żądania. Dane nie migrują między wątkami, co eliminuje narzut synchronizacji i rywalizację o linie pamięci podręcznej (cache-line contention). Ten model sprawdza się szczególnie dobrze przy obciążeniach o jednorodnym charakterze, gdzie każde żądanie wymaga zbliżonej ilości pracy.
Axum opiera się na jednym współdzielonym, wielowątkowym runtime Tokio z algorytmem work-stealing. Tokio dynamicznie przenosi zadania między wątkami w celu równomiernego rozłożenia obciążenia. Framework został zaprojektowany przez zespół Tokio jako referencyjny przykład wykorzystania ich runtime — każdy handler to zwykła funkcja async, a warstwa middleware bazuje na traicie Service z biblioteki Tower.
Poniższe dwa przykłady ilustrują minimalny serwer HTTP w każdym z frameworków.
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();
}Nawet na poziomie minimalnych przykładów widoczne są różnice projektowe. Actix Web wykorzystuje dedykowane makro #[actix_web::main] i wymaga jawnego zwracania std::io::Result. Axum korzysta ze standardowego #[tokio::main], a handler zwraca typowany ekstraktor Json<Value> zamiast ręcznie konstruowanego HttpResponse. Struktura Router w Axum pełni rolę centralnego rejestru tras, podczas gdy Actix Web buduje aplikację za pomocą App::new() z łańcuchowymi wywołaniami .route().
Porównanie wydajności: benchmarki Actix Web 4.13 vs Axum 0.8
Dane z TechEmpower Round 22 oraz niezależnych testów społecznościowych konsekwentnie wskazują ten sam wzorzec.
| Metryka | Actix Web 4.13 | Axum 0.8.9 | |--------|---------------|-------------| | Serializacja JSON (req/s) | ~720 000 | ~640 000 | | Plaintext (req/s) | ~980 000 | ~870 000 | | Zapytanie DB (req/s) | ~180 000 | ~170 000 | | Zużycie pamięci (hello world) | ~8 MB | ~6 MB | | Latencja P99 (JSON) | 1,2 ms | 1,4 ms |
Actix Web konsekwentnie utrzymuje przewagę 10-15% w zakresie surowej przepustowości. Axum odpowiada niższym zużyciem pamięci — konsekwencja współdzielonego runtime Tokio. Dla porównania: oba frameworki przewyższają typowy serwer Go (net/http) 2-3-krotnie, a Node.js 5-8-krotnie na identycznym sprzęcie.
W praktyce ta różnica ma realne znaczenie wyłącznie w scenariuszach ekstremalnej przepustowości: serwowanie reklam programatycznych, potoki ingestii danych analitycznych w czasie rzeczywistym czy bramki handlu algorytmicznego. Dla standardowego REST API obsługującego kilkanaście tysięcy żądań na sekundę wąskim gardłem i tak pozostanie baza danych lub komunikacja z usługami zewnętrznymi, nie sam framework.
Wzorce ekstraktorów i obsługa żądań
System ekstraktorów decyduje o tym, jak framework parsuje przychodzące żądania HTTP i udostępnia ich komponenty w handlerach. Axum 0.8 wprowadził istotne ulepszenia w tym obszarze — usunięto makro #[async_trait] na rzecz natywnych async traits (stabilnych od Rust 1.75) i dodano OptionalFromRequestParts do eleganckiej obsługi opcjonalnych parametrów.
use axum::{
extract::{Path, Query, State, Json},
routing::get,
Router,
};
use serde::Deserialize;
use std::sync::Arc;
#[derive(Deserialize)]
struct Pagination {
page: Option<u32>,
per_page: Option<u32>,
}
struct AppState {
db_pool: sqlx::PgPool,
}
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);
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
}))
}W Axum ekstrakcja odbywa się przez destrukturyzację krotek w sygnaturze funkcji: State(state), Path(user_id). W Actix Web parametry są opakowywane w typy generyczne (web::Data<T>, web::Path<T>, web::Query<T>), a dostęp do wartości wewnętrznych wymaga wywołania .into_inner(). Oba podejścia zapewniają bezpieczeństwo typów w czasie kompilacji, jednak składnia Axum jest bliższa idiomatycznemu Rust i czyta się bardziej naturalnie.
Warto zaznaczyć, że Axum 0.8 zmienił składnię parametrów ścieżek z /:id na /{id}, co jest zgodne ze standardem OpenAPI i eliminuje potencjalne niejednoznaczności.
Middleware: ekosystem Tower kontra system Actix Web
Architektura middleware stanowi prawdopodobnie najważniejszą praktyczną różnicę między tymi frameworkami.
Axum w pełni integruje się z biblioteką Tower, korzystając z traitów Service i Layer. Oznacza to, że każdy middleware napisany dla ekosystemu Tower — ograniczniki szybkości, warstwy tracing, kompresja HTTP, mechanizmy uwierzytelniania — działa z Axum bez żadnych modyfikacji. Co więcej, ta kompozycyjność wykracza poza protokół HTTP: ten sam middleware może opakowywać usługi gRPC realizowane przez Tonic.
use axum::{
Router, middleware,
routing::get,
extract::Request,
response::Response,
};
use tower_http::{
cors::CorsLayer,
compression::CompressionLayer,
trace::TraceLayer,
};
use std::time::Instant;
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 posiada własny, niezależny system middleware oparty na traitach Transform i Service (nie tych z Tower). Middleware napisany dla ekosystemu Tower nie jest bezpośrednio kompatybilny — wymaga napisania warstwy adaptacyjnej lub przepisania od podstaw.
Dla zespołów pracujących już z ekosystemem Tower — na przykład łączących usługi HTTP z gRPC przez Tonic lub korzystających z Hyper jako klienta HTTP — kompatybilność middleware Axum stanowi istotną przewagę operacyjną. Dla zespołów budujących izolowaną usługę HTTP własny system middleware Actix Web oferuje porównywalną funkcjonalność, choć kosztem mniejszej przenoszalności między projektami.
Gotowy na rozmowy o Rust?
Ćwicz z naszymi interaktywnymi symulatorami, flashcards i testami technicznymi.
Integracja z bazą danych: SQLx jako wspólny mianownik
SQLx to asynchroniczny zestaw narzędzi SQL dla Rust, który waliduje zapytania w czasie kompilacji. Kluczowa cecha: warstwa bazodanowa pozostaje identyczna niezależnie od tego, czy aplikacja korzysta z Actix Web, czy z Axum.
use sqlx::PgPool;
#[derive(sqlx::FromRow, serde::Serialize)]
struct User {
id: i64,
email: String,
created_at: chrono::NaiveDateTime,
}
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 = $1",
email
)
.fetch_optional(pool)
.await
}Makro query_as! łączy się z działającą instancją PostgreSQL w trakcie kompilacji i weryfikuje nazwy kolumn, typy danych oraz samo istnienie tabel. Błąd w nazwie kolumny powoduje błąd kompilacji, a nie awarię w środowisku produkcyjnym. Kompromisem jest konieczność dostępu do bazy danych w procesie budowania — w pipeline'ach CI rozwiązaniem jest polecenie sqlx prepare, które generuje metadane zapytań offline i zapisuje je w katalogu .sqlx commitowanym do repozytorium.
Obsługa błędów: IntoResponse kontra ResponseError
Wzorce obsługi błędów ujawniają odmienne podejścia projektowe obu frameworków. Axum opiera się na traicie IntoResponse w połączeniu ze standardowym typem Result. Actix Web korzysta z dedykowanego traitu ResponseError.
use axum::{
http::StatusCode,
response::{IntoResponse, Response},
Json,
};
enum AppError {
NotFound(String),
DatabaseError(sqlx::Error),
ValidationError(String),
}
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()
}
}
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 })))
}Podejście Axum pozwala na naturalne wykorzystanie operatora ? i implementacji traitu From do automatycznej konwersji błędów. Actix Web wymaga implementacji zarówno Display, jak i ResponseError dla każdego typu błędu. Oba wzorce prowadzą do bezpiecznego typowo API, jednak idiom Axum jest bliższy standardowym konwencjom Rust dotyczącym propagacji błędów.
W obu frameworkach warto zdefiniować centralny typ AppError z implementacją From<sqlx::Error>, From<serde_json::Error> i innymi konwersjami. Pozwala to na stosowanie operatora ? w całym kodzie handlerów bez jawnego mapowania błędów na każdym poziomie wywołań.
Kiedy wybrać Actix Web
Actix Web pozostaje najlepszym wyborem w następujących scenariuszach:
- Ekstremalnie wysoka przepustowość: systemy serwowania reklam programatycznych, potoki ingestii danych analitycznych, bramki handlu algorytmicznego — wszędzie tam, gdzie 10-15% dodatkowych req/s przekłada się na mierzalną wartość biznesową.
- Aplikacje oparte na WebSocket: wsparcie WebSocket w Actix Web jest dojrzalsze i sprawdzone w większej liczbie wdrożeń produkcyjnych niż odpowiednik w Axum.
- Istniejące projekty Actix Web: migracja z Actix Web 3.x do 4.x przebiega płynnie. Przepisywanie stabilnej usługi produkcyjnej na Axum przynosi malejące korzyści przy rosnącym ryzyku regresji.
- Zespoły z doświadczeniem w Actix Web: zmiana frameworka wyłącznie dla lepszej ergonomii rzadko uzasadnia koszt wdrożenia nowego narzędzia i przekwalifikowania zespołu.
Kiedy wybrać Axum
Axum jest lepszym dopasowaniem w tych kontekstach:
- Nowe projekty w 2026: kompatybilność z ekosystemem Tokio (Tonic, Hyper, Tower) minimalizuje tarcia integracyjne i redukuje liczbę zależności.
- Usługi łączące HTTP i gRPC: middleware Tower działa w obu protokołach, eliminując potrzebę pisania osobnych warstw dla każdego z nich.
- Zespoły wchodzące w ekosystem Rust: typowane ekstraktory, czytelne komunikaty błędów kompilacji i składnia
/{id}(zgodna z OpenAPI) oferują łagodniejszą krzywą nauki. - Architektury mikroserwisowe: trait
Servicez Tower umożliwia współdzielenie middleware (uwierzytelnianie, tracing, rate limiting) między wieloma usługami bez duplikacji kodu.
Pytania rekrutacyjne: Actix Web i Axum w kontekście rozmów technicznych
Rozmowy kwalifikacyjne na stanowiska backendowe w Rust coraz częściej obejmują pytania dotyczące frameworków webowych. Poniższe zagadnienia pojawiają się regularnie na rozmowach na poziomie senior i lead.
P: W jaki sposób model runtime Actix Web różni się od modelu Axum i jakie ma to konsekwencje wydajnościowe?
Actix Web tworzy osobny jednowątkowy runtime Tokio dla każdego rdzenia CPU. Zadania są przypięte do wątków i nie migrują, co eliminuje narzut work-stealingu i rywalizację o linie cache. Axum korzysta z jednego wielowątkowego runtime Tokio z work-stealingiem, który dynamicznie równoważy obciążenie. Model Actix Web generuje wyższą przepustowość pod ciągłym, jednorodnym obciążeniem. Model Axum lepiej radzi sobie z nierównomiernym obciążeniem dzięki dynamicznej redystrybucji zadań.
P: Jakie praktyczne znaczenie ma usunięcie #[async_trait] w Axum 0.8?
Axum 0.8 wykorzystuje natywne return-position impl Trait w traitach, ustabilizowane w Rust 1.75. Niestandardowe ekstraktory implementujące FromRequestParts lub FromRequest definiują metody async bez pośrednictwa makra #[async_trait]. Eliminuje to dynamiczną dyspozycję przez Box<dyn Future>, co redukuje alokacje na stercie i skraca czas kompilacji.
P: Czym różni się middleware Tower od systemu middleware w Actix Web?
Tower definiuje generyczny trait Service<Request> niezależny od protokołu transportowego. Warstwa timeout lub rate limiting napisana dla Tower działa zarówno z Axum (HTTP), jak i z Tonic (gRPC). Actix Web stosuje własne traity Transform i Service, niezgodne z ekosystemem Tower. Praktyczna konsekwencja: middleware Axum jest wielokrotnie używalny w całym stosie opartym na Tokio, middleware Actix Web jest ograniczony do frameworka.
P: W jaki sposób SQLx waliduje zapytania SQL w czasie kompilacji i jakie są z tego wynikające kompromisy?
Makro query_as! z SQLx łączy się z działającą bazą danych PostgreSQL podczas kompilacji, weryfikując składnię SQL, nazwy kolumn, typy danych i istnienie tabel. Błąd w zapytaniu powoduje błąd kompilacji. Kompromis: proces budowania wymaga dostępu do bazy danych. Rozwiązaniem dla CI jest polecenie sqlx prepare, generujące offline metadane zapytań zapisywane w katalogu .sqlx w kontroli wersji.
P: W jakim scenariuszu wybór Actix Web zamiast Axum byłby technicznie uzasadniony?
Actix Web jest uzasadniony, gdy ciągła przepustowość stanowi główne ograniczenie architektoniczne — serwowanie reklam, ingestia danych analitycznych lub systemy handlu algorytmicznego. Model przypiętych wątków eliminuje narzut work-stealingu, dając 10-15% więcej req/s. Axum jest uzasadniony, gdy kompozycyjność z ekosystemem Tower i Tokio ma wyższą wartość niż marginalne różnice w przepustowości, zwłaszcza w architekturach mikroserwisowych łączących HTTP z gRPC.
Kandydaci nierzadko próbują wskazać jeden framework jako obiektywnie lepszy. Mocne odpowiedzi bazują na analizie kompromisów: Actix Web optymalizuje przepustowość na poziomie pojedynczej usługi, Axum optymalizuje kompozycyjność i spójność ekosystemu. Właściwy wybór zależy od ograniczeń konkretnego systemu, nie od osobistych preferencji programisty.
Podsumowanie
- Actix Web 4.13 zapewnia 10-15% wyższą przepustowość dzięki modelowi runtime z przypiętymi wątkami — sprawdza się w usługach o ekstremalnych wymaganiach wydajnościowych
- Axum 0.8 oferuje lepszą ergonomię dzięki natywnym async traits, składni
/{id}zgodnej z OpenAPI i pełnej kompatybilności z middleware Tower - Warstwa bazodanowa (SQLx z walidacją w czasie kompilacji) jest identyczna dla obu frameworków — wybór frameworka nie determinuje wzorców dostępu do danych
- Axum zużywa mniej pamięci (~6 MB vs ~8 MB w scenariuszu hello world) dzięki współdzielonemu runtime Tokio
- Dla nowych projektów webowych w Rust bez ekstremalnych wymagań przepustowości Axum redukuje długoterminowy koszt utrzymania poprzez spójność z ekosystemem Tokio, Tonic i Tower
- Pytania rekrutacyjne dotyczące tego tematu weryfikują zdolność kandydata do rozumowania o kompromisach architektonicznych, a nie deklaratywną znajomość API
- Przygotowanie do rozmów kwalifikacyjnych z Rust wymaga zrozumienia decyzji projektowych obu frameworków i umiejętności argumentowania wyboru w kontekście konkretnych ograniczeń systemu
Zacznij ćwiczyć!
Sprawdź swoją wiedzę z naszymi symulatorami rozmów i testami technicznymi.
Tagi
Udostępnij
Powiązane artykuły

Async/Await w Rust: Tokio, Futures i asynchroniczna współbieżność w praktyce
Kompletny przewodnik po programowaniu asynchronicznym w Rust. Artykuł wyjaśnia mechanizm futures, runtime Tokio, spawning zadań, kanały mpsc, obsługę błędów oraz wzorce produkcyjne z ograniczaniem współbieżności.

Ownership i Borrowing w Rust: Kompletny przewodnik po zarządzaniu pamięcią
Szczegółowy przewodnik po systemie ownership i borrowing w Rust. Semantyka przenoszenia, pożyczanie niemutowalne i mutowalne, czasy życia referencji oraz typowe wzorce stosowane w praktyce i na rozmowach kwalifikacyjnych.

Ownership i Borrowing w Rust: Kompletny Przewodnik
Opanuj system ownership i borrowing w Rust. Zasady własności, referencje, lifetime i zaawansowane wzorce zarządzania pamięcią.