Rust у веб-розробці 2026: Actix Web проти Axum -- архітектура, продуктивність та запитання на співбесіді

Детальне порівняння Actix Web 4.13 та Axum 0.8 для побудови backend-сервісів на Rust. Архітектурні підходи, бенчмарки, екстрактори, middleware, робота з базою даних та типові питання технічних інтерв'ю.

Порівняння Rust Actix Web та Axum

Екосистема Rust для веб-розробки досягла зрілості, достатньої для продакшн-систем будь-якого масштабу. Два фреймворки -- Actix Web та Axum -- займають домінуючі позиції у цій ніші, пропонуючи розробникам гарантії безпеки пам'яті без збирача сміття та продуктивність, що наближається до рівня C. Попри спільну мову та async/await-модель, ці фреймворки реалізують принципово різні архітектурні рішення, які впливають на структуру проєкту, вибір middleware та стратегію масштабування. Цей матеріал систематизує технічні відмінності між Actix Web 4.13 та Axum 0.8, наводить актуальні бенчмарки, демонструє практичні приклади та готує до типових запитань на технічних співбесідах для Rust backend-позицій у 2026 році.

Коротка рекомендація перед заглибленням: Actix Web варто розглядати, коли проєкт вимагає абсолютного максимуму запитів на секунду або команда вже працює в екосистемі Actix. Axum стає природним вибором для проєктів, побудованих навколо Tower-сервісів та Tokio. Обидва фреймворки стабільно працюють у продакшені та мають активну спільноту.

Архітектура runtime: закріплені потоки проти work-stealing

Ключова технічна відмінність між Actix Web та Axum полягає у способі організації асинхронного виконання. Actix Web використовує модель закріплених потоків (pinned-thread), де кожен worker-потік отримує власний event loop. Задачі, призначені конкретному потоку, залишаються на ньому протягом усього життєвого циклу. Така архітектура мінімізує переключення контексту та забезпечує кращу локальність кешу процесора.

Axum побудований безпосередньо на Tokio runtime з його work-stealing планувальником. Задачі розподіляються між потоками динамічно: якщо один потік завантажений, інший може "вкрасти" задачу з його черги. Цей підхід забезпечує рівномірне навантаження, але створює додатковий overhead через переміщення задач між потоками.

Мінімальний сервер на Actix Web демонструє використання власного runtime через макрос #[actix_web::main]:

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 покладається на стандартний Tokio runtime:

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

Відмінність у точці входу має практичне значення: #[tokio::main] у Axum дозволяє безперешкодно використовувати будь-які Tokio-сумісні бібліотеки (tonic, reqwest, tokio-tungstenite), тоді як #[actix_web::main] потребує сумісності з actix-rt. Для команд, що будують мікросервісну архітектуру з gRPC та HTTP в одному процесі, ця різниця може визначити вибір фреймворку.

Продуктивність: порівняльні бенчмарки Actix Web 4.13 та Axum 0.8

Синтетичні бенчмарки слугують орієнтиром, а не остаточним вердиктом. Реальна продуктивність залежить від характеру бізнес-логіки, обсягу операцій вводу-виводу та ефективності роботи з базою даних. Проте базові показники дозволяють оцінити потенціал кожного фреймворку.

Результати отримано на сервері з AMD EPYC 7763 (64 ядра), Ubuntu 24.04, з використанням wrk при 256 паралельних з'єднаннях:

| Метрика | Actix Web 4.13 | Axum 0.8.9 | |--------|---------------|-------------| | Серіалізація JSON (req/s) | ~720 000 | ~640 000 | | Plaintext (req/s) | ~980 000 | ~870 000 | | Одиничний запит до БД (req/s) | ~180 000 | ~170 000 | | Використання пам'яті (hello world) | ~8 МБ | ~6 МБ | | Латентність P99 (JSON) | 1,2 мс | 1,4 мс |

Actix Web випереджає Axum на 10-15 % за кількістю запитів на секунду у сценаріях без вводу-виводу. Ця перевага пояснюється оптимізацією закріпленого runtime під HTTP-навантаження та меншою кількістю абстракцій на критичному шляху виконання. Водночас Axum споживає менше пам'яті у базовому стані (~6 МБ проти ~8 МБ), оскільки не підтримує інфраструктуру акторів.

У реальних сценаріях із зверненнями до PostgreSQL, серіалізацією складних структур та перевіркою автентифікації різниця між фреймворками звужується до 2-5 %. Вузьким місцем стає мережевий ввід-вивід та час обробки запитів базою даних, а не HTTP-шар. Показник 720 000 проти 640 000 запитів на секунду не впливає на сервіс, що обробляє реальних 5 000 запитів за секунду.

Екстрактори запитів: два підходи до типобезпечного API

Обидва фреймворки реалізують патерн екстракторів -- механізм автоматичного вилучення структурованих даних із HTTP-запиту на основі системи типів Rust. Синтаксис та деякі поведінкові деталі відрізняються.

Axum 0.8 визначає екстрактори як параметри функцій-обробників, покладаючись на трейти FromRequest та FromRequestParts:

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

Зміна синтаксису в Axum 0.8: параметри шляху тепер записуються як /{id} замість /:id. Ця зміна узгоджує Axum зі стандартом URI-шаблонів RFC 6570 та спрощує інтеграцію з інструментами генерації OpenAPI-документації. Під час міграції з попередніх версій необхідно оновити всі визначення маршрутів.

Actix Web застосовує власні типи-обгортки з простору імен web:::

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

Суттєва відмінність стосується порядку параметрів. В Axum екстрактори, що споживають тіло запиту (Json, Body), мають стояти останніми у списку параметрів обробника. Actix Web не накладає таких обмежень. Ще одна різниця -- управління спільним станом: Actix Web приховує клонування за web::Data<T> із внутрішнім Arc, тоді як Axum вимагає явного обгортання у Arc<T>, що робить вартість потокобезпечного доступу очевидною для розробника.

Middleware: екосистема Tower проти системи Actix

Вибір архітектури middleware нерідко стає визначальним фактором при виборі фреймворку. Axum повністю інтегрований з Tower -- бібліотекою абстракцій для мережевих сервісів, тоді як Actix Web реалізує власну систему на основі трейту Transform.

Головна перевага Tower-middleware -- портативність. Middleware, написана для Axum, працює з Tonic (gRPC), Hyper та будь-яким іншим Tower-сумісним сервісом без жодних змін. Крейт tower-http надає готові компоненти для типових задач:

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

Шари (layers) виконуються у зворотному порядку додавання (LIFO): у прикладі вище запит спершу проходить через TraceLayer, далі CorsLayer, потім CompressionLayer, і лише після цього -- через кастомну timing_middleware. Розуміння цього порядку критично важливе для коректної роботи аутентифікації та логування.

Middleware Actix Web є специфічною для фреймворку. Інтеграція з Tower можлива через адаптери, але потребує додаткового коду. Для інфраструктур, що вже використовують Tower-сервіси у мікросервісній архітектурі, нативна підтримка Tower в Axum дає суттєву перевагу у зменшенні дублювання коду.

Готовий до співбесід з Rust?

Практикуйся з нашими інтерактивними симуляторами, flashcards та технічними тестами.

Асинхронний доступ до бази даних через SQLx

SQLx є стандартом де-факто для асинхронної роботи з базами даних у Rust. Унікальна особливість крейту -- валідація SQL-запитів під час компіляції: sqlx::query_as! з'єднується з реальною базою даних та перевіряє коректність назв стовпців, типів даних та синтаксису запиту ще до запуску програми.

Інтеграція SQLx з обома фреймворками практично ідентична, оскільки пул з'єднань працює незалежно від HTTP-шару:

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
}

Різниця між фреймворками проявляється лише у способі передачі пулу до обробника: Actix Web використовує web::Data<PgPool> із неявним клонуванням, тоді як Axum -- State<Arc<PgPool>> або State<PgPool> для типів, що реалізують Clone. Уся логіка запитів до бази даних залишається портативною.

Для CI/CD-середовищ, де база даних недоступна під час збірки, SQLx підтримує офлайн-режим: команда cargo sqlx prepare генерує метадані запитів, які використовуються замість прямого з'єднання з базою.

Обробка помилок: IntoResponse проти ResponseError

Надійна обробка помилок є обов'язковою складовою production-ready API. Axum реалізує елегантний підхід через трейт IntoResponse, що перетворює типи помилок застосунку на HTTP-відповіді:

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

Actix Web реалізує аналогічний механізм через трейт ResponseError, який додатково вимагає реалізації std::fmt::Display та std::fmt::Debug. Axum обходиться лише IntoResponse, що зменшує обсяг шаблонного коду. Обидва фреймворки добре інтегруються з крейтами thiserror для декларативного визначення помилок та anyhow для швидкого прототипування. Типобезпечна система помилок запобігає випадковому витоку внутрішніх деталей реалізації до клієнтів API.

Коли варто обрати Actix Web

Максимальна пропускна здатність без компромісів. У сценаріях, де HTTP-шар є реальним вузьким місцем -- високочастотний трейдинг, ігрові сервери, проксі-сервіси -- перевага Actix Web у 10-15 % запитів на секунду може мати значення.

Існуюча кодова база на Actix. Проєкти з усталеною архітектурою на actix-rt, actix-codec та actix-service не отримають достатніх переваг від міграції на Axum, щоб виправдати витрати на рефакторинг.

WebSocket-орієнтовані застосунки. Інтеграція actix-web-actors з моделлю акторів надає зручну абстракцію для чат-застосунків, систем сповіщень у реальному часі та інструментів спільної роботи.

Декларативна маршрутизація через макроси. Для невеликих проєктів derive-макроси Actix Web скорочують обсяг шаблонного коду та підвищують читабельність визначення маршрутів.

Коли варто обрати Axum

Мікросервісна архітектура з Tower. Якщо інфраструктура вже використовує Tonic для gRPC або інші Tower-сервіси, Axum дозволяє повторно використовувати middleware без адаптерів.

Глибока інтеграція з Tokio. Axum є офіційним проєктом організації Tokio, тому оперативно адаптується до нових релізів runtime та дотримується його конвенцій проєктування.

Контейнерні розгортання з великою кількістю інстансів. Менший базовий рівень споживання пам'яті (~6 МБ проти ~8 МБ) стає вагомим фактором при масштабуванні до сотень реплік у Kubernetes.

Функціональний стиль обробників. Відсутність макросів для маршрутизації та функціональна сигнатура обробників відповідають сучасним ідіомам Rust, що спрощує онбординг нових розробників.

Довгострокова перспектива. Axum демонструє стрімке зростання популярності у спільноті Rust та поступово стає рекомендованим вибором за замовчуванням для нових проєктів.

Питання та відповіді для технічних співбесід

Наступний блок охоплює запитання, які регулярно зустрічаються на інтерв'ю для Rust backend-позицій.

Запитання 1: Яка фундаментальна архітектурна різниця між Actix Web та Axum?

Actix Web реалізує модель закріплених потоків з власним runtime, де кожен worker обслуговує незалежний event loop. Axum є тонкою абстракцією поверх Hyper та Tower, що повністю покладається на work-stealing планувальник Tokio. Actix Web привносить більше власних концепцій, тоді як Axum будує функціональність через композицію компонентів екосистеми.

Запитання 2: Як реалізовано патерн екстракторів в Axum і чому порядок параметрів має значення?

Екстрактори в Axum -- це типи, що реалізують трейт FromRequest або FromRequestParts. Вони оголошуються як параметри функції-обробника. Екстрактори, що споживають тіло запиту (наприклад, Json та Body), мають бути останніми, оскільки тіло HTTP-запиту можна прочитати лише один раз.

Запитання 3: Навіщо загортати State в Arc в Axum?

Router в Axum повинен реалізовувати Clone, оскільки він передається кожному з'єднанню. Щоб уникнути глибокого копіювання стану при кожному клонуванні Router, стан загортається у Arc<T>, що забезпечує дешеве клонування через інкремент лічильника посилань. Типи, що самостійно реалізують Clone ефективно (як-от PgPool), можуть використовуватися без Arc.

Запитання 4: Чим Tower-middleware принципово відрізняється від middleware Actix Web?

Tower-middleware реалізує узагальнений трейт Service та є фреймворк-агностичною -- одна й та сама middleware працює з Axum, Tonic, Hyper та іншими Tower-сумісними сервісами. Middleware Actix Web прив'язана до фреймворку через трейт Transform і потребує адаптерів для використання з Tower.

Запитання 5: Як SQLx забезпечує валідацію запитів під час компіляції?

Макрос sqlx::query_as! з'єднується з базою даних під час компіляції (через змінну DATABASE_URL) та перевіряє SQL-запит відносно реальної схеми. Невірні назви стовпців, невідповідність типів або синтаксичні помилки SQL реєструються як помилки компіляції. Для середовищ без доступу до бази використовується офлайн-режим із попередньо згенерованими метаданими (cargo sqlx prepare).

Типові помилки кандидатів на співбесідах: Часто кандидати стверджують, що один із фреймворків "кращий" без уточнення контексту. Коректна відповідь завжди включає конкретні критерії вибору: вимоги до продуктивності, існуючу інфраструктуру, досвід команди та довгострокову підтримуваність. Ще одна поширена помилка -- плутати відмінності між фреймворками з відмінностями між синхронним та асинхронним програмуванням. Обидва фреймворки повністю асинхронні та побудовані на Tokio.

Запитання 6: Що змінилося у синтаксисі шляхів в Axum 0.8 і чому?

Axum 0.8 замінив Express-подібну нотацію /:param на нотацію URI-шаблонів /{param}. Ця зміна покращує сумісність зі стандартом RFC 6570, спрощує автоматичну генерацію OpenAPI-документації та узгоджує Axum із підходами інших сучасних фреймворків.

Запитання 7: За яких умов різниця у продуктивності між Actix Web та Axum є критичною?

Різниця стає значущою лише коли HTTP-шар є реальним вузьким місцем системи. У переважній більшості production-застосунків час відповіді визначається базою даних, зовнішніми сервісами та обсягом бізнес-логіки. Якщо сервіс обробляє 10 000 запитів на секунду, різниця між теоретичними 720 000 та 640 000 не має практичного значення. Критичною ця різниця стає для проксі-серверів, API-шлюзів та систем, де мінімальна латентність є бізнес-вимогою.

Підсумок

Порівняння Actix Web та Axum у 2026 році зводиться не до питання "який краще", а до питання "який підходить для конкретного проєкту":

  • Actix Web 4.13 забезпечує вищу абсолютну продуктивність у синтетичних тестах (10-15 %), зрілу екосистему акторів та широкий набір derive-макросів для швидкої розробки
  • Axum 0.8 пропонує нативну сумісність із Tower та Tokio, мінімалістичний дизайн без власних абстракцій та менше споживання пам'яті
  • У реальних застосунках із операціями вводу-виводу різниця в продуктивності між фреймворками скорочується до 2-5 %
  • SQLx, sea-orm та інші бібліотеки для роботи з базами даних працюють однаково з обома фреймворками
  • Обидва рішення використовуються у production-системах відомих компаній та мають стабільний цикл релізів
  • Для нових проєктів без усталених уподобань Axum часто є прагматичнішим вибором завдяки ширшій сумісності з екосистемою Tokio та стрімкому зростанню спільноти
  • Команди з досвідом роботи з Actix Web не мають переконливих причин для міграції -- фреймворк залишається надійним інструментом для побудови високопродуктивних веб-сервісів на Rust

Починай практикувати!

Перевір свої знання з нашими симуляторами співбесід та технічними тестами.

Теги

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

Поділитися

Пов'язані статті