Rust Webフレームワーク比較:Actix Web vs Axum — 2026年版ガイドと面接対策

Actix Web 4.13とAxum 0.8のアーキテクチャ、パフォーマンス、ミドルウェア設計を比較。2026年のRustバックエンド面接で頻出する質問と回答例も解説します。

Rust Webフレームワーク比較:Actix Web vs Axum 2026

2026年、Rustによるバックエンド開発はもはや実験的な選択肢ではなくなりました。本番環境でのRust Webフレームワーク採用が加速する中、Actix Web 4.13とAxum 0.8の2つが業界標準として定着しています。どちらを選ぶかによって、チームのオンボーディング速度から本番環境のスループットまで、あらゆる側面に影響が及びます。

フレームワーク選択の判断基準

Actix Web 4.13は高負荷時に10〜15%高いリクエスト処理能力を発揮します。一方、Axum 0.8はネイティブasync trait、Towerミドルウェアの互換性、Tokioランタイムとの統合により、優れた開発体験を提供します。2026年の新規プロジェクトでは、極端なスループット要件がない限りAxumが実用的な第一選択肢です。

Actix WebとAxumのアーキテクチャの違い

両フレームワークのアーキテクチャの違いが、パフォーマンスと開発体験のトレードオフの大部分を説明します。

Actix Webは物理コアごとに1つのシングルスレッドTokioランタイムを生成します。タスクはスレッドに固定(ピン留め)され、スレッド間のデータ移動は発生しません。これによりワークスティーリングのオーバーヘッドとキャッシュラインの競合が排除され、持続的な高負荷でのスループット優位性が実現されます。

Axumはワークスティーリング機能を持つ単一のマルチスレッドTokioランタイム上で動作します。TokioチームがAxumを設計した目的は、ランタイムの能力を最大限に示すことであり、すべての設計判断がTokioエコシステムとの互換性を最適化しています。ハンドラは通常のasync関数であり、ミドルウェアにはTowerServiceトレイトが使用されます。

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

両方のコードはコンパイル・実行可能ですが、違いは明確です。Actix Webは独自の#[actix_web::main]マクロを使用し、std::io::Resultを返します。Axumは標準の#[tokio::main]を使用し、Router構造体でルートを構築します。Axumのハンドラは型付きエクストラクタ(Json<Value>)を返し、HttpResponseを手動で構築する必要がありません。

パフォーマンスベンチマーク:Actix Web 4.13 vs Axum 0.8

TechEmpower Round 22およびコミュニティの再現可能なテストから得られたベンチマークデータは、一貫したパターンを示しています。

| 指標 | Actix Web 4.13 | Axum 0.8.9 | |------|---------------|-------------| | JSONシリアライゼーション(req/s) | 約720,000 | 約640,000 | | プレーンテキスト(req/s) | 約980,000 | 約870,000 | | DB単一クエリ(req/s) | 約180,000 | 約170,000 | | メモリ使用量(hello world) | 約8 MB | 約6 MB | | P99レイテンシ(JSON) | 1.2 ms | 1.4 ms |

Actix Webはすべてのカテゴリで10〜15%のスループット優位性を維持しています。Axumは共有Tokioランタイムのおかげで、わずかにメモリ使用量が少なくなっています。参考として、両フレームワークともGoの標準ライブラリHTTPサーバーの2〜3倍、Node.jsの5〜8倍のパフォーマンスを同等のハードウェアで発揮します。

このパフォーマンス差が重要になるのは、広告配信、リアルタイム分析パイプライン、高頻度取引ゲートウェイなどの分野です。毎秒10,000リクエストを処理する一般的なREST APIでは、両フレームワークともボトルネックをはるかに上回っており、実際のボトルネックはデータベースや外部サービスの呼び出しにあります。

エクストラクタとリクエスト処理の比較

エクストラクタは、フレームワークが受信リクエストをどのようにパースするかを定義します。Axum 0.8では#[async_trait]を削除しネイティブasync traitを採用したほか、OptionalFromRequestPartsの導入により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
    }))
}

Axumのエクストラクタは関数パラメータでタプル分解を直接使用します。Actix Webはすべてをweb::Pathweb::Queryなどでラップし、.into_inner()の呼び出しが必要です。どちらもコンパイル時に型安全性が保証されますが、Axumの方がより自然な読みやすさを持っています。

Axum 0.8の破壊的変更

Axum 0.8では、パスパラメータの構文が/:idから/{id}に変更されました(matchit 0.8経由)。これはOpenAPIのパス構文と統一されています。リテラルの{をエスケープするには、二重中括弧{{を使用します。

ミドルウェアアーキテクチャ:Tower vs Actixミドルウェア

ミドルウェアの構成は、アーキテクチャの違いが最も実用的な影響を与える領域です。

AxumはTowerのServiceLayerトレイトを使用します。Tower互換のミドルウェア(レートリミッタ、トレーシング、圧縮、認証レイヤーなど)はすべてAxumで動作します。この互換性はHTTPを超えて拡張され、Tonicを通じてgRPCサービスにも同じミドルウェアを適用できます。

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は独自のミドルウェアシステムを持ち、TransformServiceトレイト(Towerのものとは異なる)を使用します。Towerエコシステムのミドルウェアを利用するには、アダプタの作成または書き直しが必要です。

Tonic(gRPC)やHyperを通じてTowerエコシステムに既に投資しているチームにとって、Axumのミドルウェアは大きな利点です。スタンドアロンのHTTPサービスを構築するチームにとっては、Actix Webのミドルウェアシステムも同等の機能を備えていますが、相互運用性はありません。

Rustの面接対策はできていますか?

インタラクティブなシミュレーター、flashcards、技術テストで練習しましょう。

SQLxによるデータベース統合

両フレームワークとも、コンパイル時にクエリを検証するasyncファーストのSQLツールキットであるSQLxと良好に連携します。統合パターンにはわずかな違いがあります。

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 = $1",
        email
    )
    .fetch_optional(pool)
    .await
}

データベースレイヤーはフレームワークの選択に関係なく同一です。SQLxのquery_as!マクロはコンパイル時にライブデータベースに接続し、カラム名、型、テーブルの存在を検証します。カラム名のタイプミスはランタイムクラッシュではなくコンパイルエラーとなります。

エラーハンドリングパターンの比較

エラーハンドリングには、両フレームワークの設計思想の違いが表れています。Actix WebはResponseErrorトレイトの実装を使用し、AxumはIntoResponseResult型の組み合わせに依存しています。

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

Axumのアプローチは、Rustの?演算子とResult型と自然に組み合わせることができます。Actix WebではResponseErrorを通じて同じことを実現しますが、DisplayResponseErrorの両方のトレイトを実装する必要があります。どちらも動作しますが、Fromトレイトとエラー伝播に慣れたRust開発者にとって、Axumのパターンの方がより慣用的に感じられます。

Actix Webを選ぶべき場合

以下のシナリオでは、Actix Webが適切な選択肢となります。

  • 最大スループットが求められる場合:広告配信、リアルタイムビッディング、分析データ取り込みパイプラインなど、10〜15%のreq/s向上がトレードオフに見合う場面です。
  • WebSocket多用のアプリケーション:Actix WebのWebSocketサポートは、より多くの本番環境で実績があります。AxumのWebSocketサポート(axum::extract::ws経由)も良好ですが、本番での実績はまだ短いです。
  • 既存のActix Webコードベース:Actix Web 3.xから4.xへの移行は簡単です。安定したサービスをAxumに書き直しても、得られる利益は限定的です。
  • チームの習熟度:チームが既にActix Webに精通している場合、エルゴノミクスの改善のためにフレームワークを切り替えることは、短期的にはほとんど利益をもたらしません。

Axumを選ぶべき場合

以下の状況では、Axumの方が適しています。

  • 2026年の新規プロジェクトTokioエコシステム(Tonic、Hyper、Tower)との連携により、統合時の摩擦が軽減されます。
  • gRPCとHTTPの混在サービス:Towerミドルウェアはアダプタレイヤーなしで両方のプロトコルで動作します。
  • Rust初心者のチーム:Axumの型駆動エクストラクタとコンパイル時エラーメッセージは、より穏やかな学習曲線を提供します。/{id}パス構文(OpenAPIと統一)は即座に馴染みがあります。
  • マイクロサービスアーキテクチャ:TowerのServiceトレイトにより、サービス間でミドルウェアを再利用でき、ボイラープレートが削減されます。

面接対策:Rustバックエンドのフレームワーク質問

バックエンドのRust面接では、Webフレームワークに関する知識がますます問われるようになっています。以下は、シニアバックエンドおよびシステムエンジニアリングの面接で出題される質問です。

Q: Actix WebとAxumのランタイムモデルのアーキテクチャ上の違いを説明してください。

Actix WebはCPUコアごとに1つのシングルスレッドTokioランタイムを生成します。タスクはスレッドに固定され、ワークスティーリングのオーバーヘッドが排除されます。Axumは共有のマルチスレッドTokioランタイム上でワークスティーリングを行います。Actix Webのモデルは高負荷時のキャッシュライン競合を削減し、より高いスループットを実現します。Axumのモデルは、すべてのタスクが1つのランタイムを共有するため、共有状態の管理が簡素化されます。

Q: Axum 0.8における#[async_trait]の削除は、カスタムエクストラクタにどのような影響を与えますか?

Axum 0.8は、2023年後半に安定化されたRustのネイティブなreturn-position impl Trait in traitsを活用しています。FromRequestPartsまたはFromRequestを実装するカスタムエクストラクタは、#[async_trait]属性なしでasyncメソッドを直接定義できるようになりました。これによりBox<dyn Future>からのヒープ割り当てが排除され、コンパイル時間が改善されます。既存のエクストラクタはマクロの削除とトレイト実装の調整が必要です。

Q: TowerミドルウェアとActix Webミドルウェアの違いを説明してください。

Towerはプロトコル非依存の汎用Service<Request>トレイトを定義しています。Towerのタイムアウトレイヤーは、HTTP(Axum)、gRPC(Tonic)、およびカスタムプロトコルで動作します。Actix Webのミドルウェアは、フレームワーク固有のTransformServiceトレイトを使用します。実用的な影響として、AxumのミドルウェアはTowerエコシステム全体で再利用可能であり、Actix Webのミドルウェアはフレームワーク固有です。

Q: SQLxのコンパイル時クエリ検証の仕組みとそのトレードオフは何ですか?

SQLxのquery_as!マクロは、コンパイル中にライブのPostgreSQLデータベースに接続します。SQL構文、カラム名、型、テーブルの存在を検証します。トレードオフとして、ビルドにデータベースアクセスが必要となり、CIパイプラインが複雑になります。SQLxはsqlx prepareコマンドでオフラインのクエリメタデータを生成でき、検証結果をバージョン管理にコミットされる.sqlxディレクトリにキャッシュします。

Q: AxumよりもActix Webを選ぶことが技術的に正しい判断となるのはどのような場合ですか?

Actix Webが正しい選択となるのは、持続的なスループットが主要な制約である場合です。広告配信、リアルタイム分析データの取り込み、高頻度取引ゲートウェイなどが該当します。ピン留めスレッドのランタイムモデルによりワークスティーリングのオーバーヘッドが排除され、高負荷時に10〜15%高いreq/sを実現します。Axumが正しいのは、特にHTTPとgRPCの両方を使用するマイクロサービスアーキテクチャにおいて、Tokioエコシステムとの互換性がわずかなスループットの差よりも重要な場合です。

面接での典型的な失敗

一方のフレームワークが普遍的に優れていると主張する候補者が多く見られます。強い回答はトレードオフを認識します。Actix Webはスループットを最適化し、Axumはエコシステムの互換性を最適化しています。正しい選択はシステムの制約に依存し、個人の好みではありません。

まとめ

  • Actix Web 4.13は、ピン留めスレッドのランタイムモデルにより10〜15%高いスループットを実現し、レイテンシに敏感な高スループットサービスに適した選択肢です
  • Axum 0.8は、ネイティブasync trait、/{id}パス構文、HTTP・gRPC間でのTowerミドルウェア完全互換性により、優れた開発体験を提供します
  • 両フレームワークとも同じデータベースレイヤー(コンパイル時検証付きSQLx)を使用するため、フレームワークの選択はデータアクセスパターンに影響しません
  • 極端なスループット要件のない2026年の新規Rust Webプロジェクトでは、AxumのTokio・Tonic・Towerとのエコシステム統合が長期的な保守コストを削減します
  • この分野の面接質問は、ランタイムモデル、ミドルウェアアーキテクチャ、トレードオフの推論力を試すものであり、フレームワークの好みではありません
  • Rust面接の準備には、両フレームワークのアーキテクチャ上の判断を理解することが必要であり、APIの構文だけでは不十分です

今すぐ練習を始めましょう!

面接シミュレーターと技術テストで知識をテストしましょう。

タグ

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

共有

関連記事