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.

Rust Actix Web vs Axum comparison

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.

Kluczowy wniosek

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.

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();
}

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.

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>,
    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
    }))
}
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
    }))
}

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.

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;

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.

shared_db.rs - SQLx with compile-time query validationrust
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.

axum_errors.rs - Axum error handling with IntoResponserust
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.

Wskazówka praktyczna

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 Service z 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.

Błąd często popełniany na rozmowach

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

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

Udostępnij

Powiązane artykuły