Web APIの言語選定で「GoかRustか」という議論が繰り返されています。結論から述べると、開発速度とチームの立ち上がりを重視するならGo、レイテンシやメモリ効率を限界まで追求するならRustが適しています。ただし、プロジェクトの規模・チーム構成・SLA要件によって最適解は変わるため、単純な二択では片付きません。

GoとRustはともにコンパイル型・静的型付け言語であり、PythonやNode.jsからの移行先として注目を集めています。しかし、両者の設計思想は根本的に異なり、その違いがWeb API開発における体験やアウトプットに大きく影響します。

GoとRustが注目される背景

クラウドインフラのコスト最適化が経営課題として浮上し、サーバーサイドの実行効率がこれまで以上に重視されるようになりました。PythonやNode.jsで構築したAPIサーバーが、トラフィック増加に伴いCPU・メモリコストを押し上げるケースは珍しくありません。

GoはGoogleが2009年に公開し、Kubernetes・Docker・Terraformといったクラウドインフラの中核ツールが採用したことで、バックエンド開発における存在感を確立しました。2025年8月にリリースされたGo 1.25では、コンテナ環境でのGOMAXPROCS自動調整や実験的GC(greenteagc)による低レイテンシ化が導入されました。2026年2月にはGo 1.26のリリースが予定されており、greenteagcのデフォルト有効化が見込まれています。

Rustは2015年にMozillaから1.0が公開され、Stack Overflow Developer Surveyで「最も愛されている言語」の座を長年維持しています。2026年2月時点でRust 1.93が最新安定版であり、Rust 2024 Editionの導入(Rust 1.85、2025年2月)によって非同期プログラミングの書き心地が改善されました。

設計哲学の根本的な違い

GoとRustを比較するうえで最も重要なのは、言語の設計目標が異なる点です。

Goの設計方針は「Simple is better」 です。言語仕様を意図的に小さく保ち、チーム全員が同じスタイルでコードを書けることを優先しています。ジェネリクスが導入されたのはGo 1.18(2022年3月)と比較的遅く、それまでは「コードの重複よりも読みやすさを優先する」という立場を貫いていました。

Rustの設計方針は「Safety without sacrifice」 です。所有権(Ownership)と借用チェッカー(Borrow Checker)により、メモリ安全性をコンパイル時に保証します。ガベージコレクション(GC)なしでメモリリークやデータ競合を防ぐ代わりに、開発者はコンパイラとの対話に多くの時間を費やすことになります。

Web API開発においてこの設計思想の差は、以下のように現れます。

観点GoRust
新機能の追加速度高い – 言語仕様がシンプルで迷いが少ない低〜中 – コンパイラの要求を満たすコード設計が必要
コードレビューの負荷低い – 書き方のバリエーションが少ない中〜高 – ライフタイムやトレイト境界の理解が前提
ランタイムの予測可能性GCによるテールレイテンシが発生しうるGCなし – レイテンシが安定
バイナリサイズ数MB〜数十MB(ランタイム込み)数百KB〜数MB(最適化時)

Web APIフレームワークの選択肢(2026年時点)

Goの主要フレームワーク

フレームワークGitHub Star特徴適したユースケース
Gin約88,000高速ルーター(httprouter)ベース、豊富なミドルウェアREST API全般、大規模プロジェクト
Echo約32,000軽量・高速、自動TLS対応軽量API、プロトタイプ
Chi約22,000標準net/http互換のルーター標準ライブラリとの統合重視
標準 net/httpGo 1.22でルーティングパターンが強化依存を最小化したい場合

Go 1.22(2024年2月リリース)で標準ライブラリのnet/httpにメソッドベースのルーティング(GET /users/{id}形式)が追加されたことで、小規模なAPIであればサードパーティフレームワークなしでも開発可能になりました。

Rustの主要フレームワーク

フレームワークGitHub Star特徴適したユースケース
Axum約25,000tokio-rs公式、Towerミドルウェア互換新規プロジェクトの第一選択
Actix-web約24,000高ベンチマークスコア、独自ランタイム最大スループットが必要な場合
Rocket約26,000直感的なAPI設計、型安全なリクエスト解析学習目的、中小規模プロジェクト

Rust側ではAxumがtokio-rsプロジェクトの公式フレームワークとして急成長しており、Towerエコシステムとの統合による拡張性が評価されています。新規プロジェクトではAxumを選択する開発者が増えています。

フレームワーク特性の比較

比較軸Go(Gin)Rust(Axum)
非同期モデルgoroutine(プリエンプティブ)async/await + tokio(協調的)
ミドルウェアGin独自のHandlerChainTower互換レイヤー
JSONシリアライズencoding/json(リフレクション)serde(コンパイル時コード生成)
ルーティング定義メソッドチェーン形式関数合成形式
入門しやすさ高い中程度(型パズルが発生しやすい)

APIエンドポイントのコード比較

Go(Gin)でのREST API実装例

package main

import (
    "net/http"
    "github.com/gin-gonic/gin"
)

type User struct {
    ID   int    `json:"id"`
    Name string `json:"name"`
}

func main() {
    r := gin.Default()

    r.GET("/users/:id", func(c *gin.Context) {
        id := c.Param("id")
        user := User{ID: 1, Name: "Alice"}
        // 実際にはDBから取得
        c.JSON(http.StatusOK, user)
    })

    r.POST("/users", func(c *gin.Context) {
        var user User
        if err := c.ShouldBindJSON(&user); err != nil {
            c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
            return
        }
        c.JSON(http.StatusCreated, user)
    })

    r.Run(":8080")
}

Rust(Axum)でのREST API実装例

use axum::{
    extract::Path,
    http::StatusCode,
    routing::{get, post},
    Json, Router,
};
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize)]
struct User {
    id: i32,
    name: String,
}

async fn get_user(Path(id): Path<i32>) -> Json<User> {
    // 実際にはDBから取得
    Json(User { id, name: "Alice".to_string() })
}

async fn create_user(
    Json(user): Json<User>,
) -> (StatusCode, Json<User>) {
    (StatusCode::CREATED, Json(user))
}

#[tokio::main]
async fn main() {
    let app = Router::new()
        .route("/users/{id}", get(get_user))
        .route("/users", post(create_user));

    let listener = tokio::net::TcpListener::bind("0.0.0.0:8080")
        .await
        .unwrap();
    axum::serve(listener, app).await.unwrap();
}

両者を並べると、Goのほうがコード量が少なく、エラーハンドリングも直感的です。一方、RustではJson<User>のように型レベルでリクエスト/レスポンスの構造が表現されるため、型の不整合がコンパイル時に検出されます。

エラーハンドリングのアプローチ

Goでは関数が(値, error)のタプルを返し、呼び出し側がif err != nilで逐次チェックします。Web APIでは以下のようなパターンが典型的です。

func getUser(c *gin.Context) {
    user, err := db.FindUser(c.Param("id"))
    if err != nil {
        c.JSON(http.StatusNotFound, gin.H{"error": "user not found"})
        return
    }
    c.JSON(http.StatusOK, user)
}

RustではResult<T, E>型と?演算子を使い、エラーを型安全に伝搬します。thiserrorクレートでアプリケーション固有のエラー型を定義し、IntoResponseトレイトを実装してHTTPレスポンスへ変換するのが一般的です。

use axum::{http::StatusCode, response::IntoResponse, Json};
use thiserror::Error;

#[derive(Error, Debug)]
enum AppError {
    #[error("user not found")]
    NotFound,
    #[error("database error: {0}")]
    Database(#[from] sqlx::Error),
}

impl IntoResponse for AppError {
    fn into_response(self) -> axum::response::Response {
        let (status, msg) = match self {
            AppError::NotFound => (StatusCode::NOT_FOUND, self.to_string()),
            AppError::Database(_) => (StatusCode::INTERNAL_SERVER_ERROR, "internal error".to_string()),
        };
        (status, Json(serde_json::json!({"error": msg}))).into_response()
    }
}

Rustのアプローチは定義量が多い一方、エラーの網羅性がコンパイラで保証されるため、運用中に想定外のエラーが未処理のまま返されるリスクが低くなります。

パフォーマンス特性の比較

スループットとレイテンシ

TechEmpower Framework Benchmarks Round 23(2025年3月公開)の結果では、Rustフレームワーク(Actix-web、may-minihttp)がほぼ全カテゴリでトップクラスの性能を記録しています。Goフレームワーク(Gin、fasthttp)も上位に位置しますが、JSON処理やDB操作を含む複合ワークロードではRustとの差が開く傾向にあります。

この差の主因はJSONシリアライズの実装方式にあります。Goのencoding/jsonはリフレクションを使用するため実行時のオーバーヘッドが発生しますが、Rustのserdeはコンパイル時にシリアライズ/デシリアライズのコードを生成するため、ゼロコスト抽象化を実現しています。

メモリ消費とGCの影響

GoのGCは、通常のAPIリクエスト処理では十分に高速です。しかし、ヒープ上のオブジェクトが大量に存在する場合(例: インメモリキャッシュ、大量のWebSocket接続管理)、GCのスキャンがテールレイテンシを引き起こすことがあります。

Discordが2020年にRead States(既読管理)サービスをGoからRustへ移行した事例が有名です。Go版では約2分ごとにGCが発動し、10〜40ミリ秒のレイテンシスパイクが発生していました。Rust版ではGCが存在しないためスパイクが消失し、レイテンシが大幅に改善されたと報告されています。

ただし、一般的なCRUD主体のWeb APIでは、GoのGCがボトルネックになるケースは限定的です。Go 1.25で導入された実験的GC(greenteagc)により、停止時間がさらに短縮される方向で改善が進んでいます。

コンテナ環境での実行効率

指標GoRust
バイナリサイズ(Hello World API)約6〜8MB約1〜3MB(strip済み)
コンテナイメージ(distroless)約15〜20MB約5〜10MB
起動時間数十ミリ秒数十ミリ秒
メモリフットプリント(アイドル時)約10〜20MB約2〜5MB

Rustはバイナリサイズとメモリフットプリントが小さいため、大量のマイクロサービスをコンテナでデプロイする環境ではクラウドコストの削減に寄与します。一方、Goもシングルバイナリでデプロイ可能であり、コンテナ化の手軽さでは引けを取りません。

並行処理モデルの違い

Goのgoroutine

Goの並行処理はgoroutineとchannelで実現されます。goroutineはGoランタイムが管理する軽量スレッドで、OSスレッドに多重化されます。goキーワードを付けるだけで非同期処理が起動でき、channelでデータを安全に受け渡します。

// 複数のAPIを並行呼び出し
func fetchAll(urls []string) []Response {
    ch := make(chan Response, len(urls))
    for _, url := range urls {
        go func(u string) {
            resp := fetch(u)
            ch <- resp
        }(url)
    }
    results := make([]Response, 0, len(urls))
    for range urls {
        results = append(results, <-ch)
    }
    return results
}

goroutineの初期スタックサイズは最小2KBと小さく、数十万のgoroutineを同時に起動しても問題ありません。Web APIサーバーでは、各リクエストが独立したgoroutineで処理されるため、開発者が並行性を明示的に管理する必要がほとんどありません。

Rustのasync/await

Rustの非同期処理はasync/awaitと非同期ランタイム(通常はtokio)で構成されます。async fnFutureトレイトを実装する型を返し、.awaitで中断・再開を制御します。

// 複数のAPIを並行呼び出し
async fn fetch_all(urls: Vec<String>) -> Vec<Response> {
    let futures: Vec<_> = urls
        .into_iter()
        .map(|url| tokio::spawn(async move { fetch(&url).await }))
        .collect();
    let mut results = Vec::new();
    for f in futures {
        results.push(f.await.unwrap());
    }
    results
}

Rustのasync/awaitはゼロコスト抽象化であり、Futureはステートマシンにコンパイルされます。ヒープアロケーションが最小限に抑えられるため、高スループット環境でのオーバーヘッドがgoroutineよりも小さくなるケースがあります。

ただし、Rustの非同期プログラミングには落とし穴があります。Sendトレイト境界や'staticライフタイムの要求により、非同期関数をまたいでデータを共有する際にコンパイルエラーが頻発します。Goのgoroutineのような「とりあえずgoを付けるだけ」の手軽さはありません。

開発体験(DX)と生産性

学習コストと立ち上がり速度

Go公式のチュートリアル「A Tour of Go」は数時間で完了でき、Go経験のない開発者でも数週間でAPI開発が可能になります。言語仕様が小さいため、チームメンバー間のコードスタイルの差が生まれにくい点も大規模開発に有利です。

Rustは所有権・ライフタイム・トレイト境界の概念を理解するまでに数ヶ月かかるのが一般的です。コンパイラのエラーメッセージは親切で改善が続いていますが、初学者にとっては「なぜコンパイルが通らないのか」を理解すること自体が学習過程となります。

コンパイル時間

条件GoRust
初回ビルド(中規模API)数秒数分〜十数分
インクリメンタルビルド1秒未満数秒〜数十秒
CI/CDパイプライン短い長い(キャッシュ活用で改善可能)

Goのコンパイル速度は言語設計の中核にある要件であり、開発中のフィードバックループが極めて短いのが強みです。Rustはコンパイル時に多くの最適化と安全性チェックを行うため時間がかかりますが、cargo-chefやsccache等のキャッシュツールでCI/CDの所要時間を短縮できます。

IDE・ツールチェイン

GoのLSP実装であるgoplsと、RustのLSP実装であるrust-analyzerはいずれも高い完成度に達しています。VS Code、JetBrains IDE(GoLand / RustRover)の両方で快適な開発体験を得られます。

Goはgo fmt(コードフォーマッタ)、go vet(静的解析)、go test(テスト)が標準ツールとして同梱されており、追加設定なしで統一的な開発体験を提供します。Rustでもrustfmtclippycargo testが標準ツールチェインに含まれています。

プロジェクト特性別の選定基準

Goが適しているケース

  • チーム規模が5人以上で、メンバーのプログラミング経験にばらつきがある
  • マイクロサービスを短期間で複数立ち上げる必要がある
  • DevOps・インフラツールとの親和性を重視する(Kubernetes、Prometheus等と同じ言語)
  • MVPやプロトタイプを素早く構築し、市場検証を優先する
  • gRPCサービスを構築する(GoのgRPCサポートは成熟している)

Rustが適しているケース

  • レイテンシのP99 SLAが厳しい(例: 10ms以下を保証する必要がある)
  • 大量のWebSocket接続やリアルタイム処理を長時間安定して稼働させる
  • クラウドコストの最適化が優先事項で、メモリ・CPU効率を最大化したい
  • WebAssemblyでの実行を視野に入れている
  • セキュリティクリティカルな領域で、メモリ安全性の保証が求められる

選定の判断マトリクス

条件Goが有利Rustが有利
チーム規模大(5人以上)小〜中(1〜4人のエキスパート)
開発スピード要件高い(短納期)中程度(品質優先)
P99レイテンシ要件50ms以上で許容10ms以下が必要
メモリ効率標準的でOK厳しい最適化が必要
運用期間短〜中期長期(5年以上)
チームの言語経験Go未経験者でも可Rust経験者が在籍

GoとRustを併用するハイブリッド構成

「GoかRustか」という二者択一に縛られず、両方を組み合わせるアーキテクチャも選択肢に入ります。

パターン1: APIゲートウェイ(Go)+ 計算集約サービス(Rust) 認証・ルーティング・レート制限などのAPIゲートウェイ層をGoで構築し、画像処理・暗号化・データ変換などの計算負荷が高い処理をRustのマイクロサービスとして切り出す構成です。サービス間通信にはgRPCやメッセージキューを使用します。

パターン2: Go APIからRust処理をFFI経由で呼び出す CGO(Go言語のC連携機構)を介して、RustでコンパイルしたCライブラリを呼び出す方法です。ただし、CGOにはgoroutineのスケジューリングに影響を与えるオーバーヘッドがあるため、呼び出し頻度が高い処理には向きません。

パターン3: WebAssemblyを介した統合 Rustで書いたロジックをWasmにコンパイルし、GoのWasmランタイム(wasmtime-go等)から呼び出す方式です。サンドボックス化されたセキュアな実行環境を構築できますが、呼び出しオーバーヘッドが大きいため、リアルタイム処理には不向きです。

実務上はパターン1が最も現実的であり、GoとRustそれぞれの長所を活かしながら段階的にRustの領域を広げていくアプローチが取られています。

エコシステムとコミュニティの成熟度

DB接続ライブラリ

言語ORM / クエリビルダー特徴
GoGORMフルORMで機能豊富、採用実績が豊富
Gosqlx生SQLに型安全性を付加、軽量
GoBun高速ORM、PostgreSQLに強い
RustDieselコンパイル時クエリ検証、型安全
RustSeaORMasync対応の動的ORM
Rustsqlxコンパイル時SQLチェック、async対応

GoのGORMは「とりあえずGORM」と言えるほどデファクトスタンダードの地位を確立しています。Rustではsqlx(コンパイル時にSQLの妥当性を検証する)とSeaORM(ActiveRecordパターン)が主流ですが、Goに比べるとエコシステムの選択肢が分散している状態です。

パッケージレジストリの規模

GoのパッケージはGoモジュールとしてpkg.go.devに登録され、Rustのクレートはcrates.ioに登録されます。crates.ioには2026年時点で22万以上のクレートが登録されており、Go側もモジュール数は増加を続けています。Web API開発に必要な基本ライブラリ(HTTP、JSON、DB接続、認証、ロギング)は両言語ともに十分に揃っています。

コミュニティと採用企業

GoはGoogle、Uber、Twitch、Mercari、LINE等の大規模サービスで採用されており、特にマイクロサービスアーキテクチャとの親和性が評価されています。

RustはAWS(Firecracker、Bottlerocket)、Discord、Cloudflare、Dropbox等で採用されています。特にインフラレイヤーやパフォーマンスクリティカルなサービスでの採用が目立ちます。

よくある疑問への回答

Web APIにRustを選ぶのはオーバーエンジニアリングか?

プロジェクトの要件次第です。CRUD中心のBtoB管理画面APIであれば、Rustの型安全性やメモリ効率の恩恵よりも、Go(またはPython/Node.js)の開発速度の方がビジネス価値に直結します。一方、秒間数万リクエストを捌くリアルタイムAPIや、レイテンシのテール値が課金に影響するようなサービスでは、Rustの選択は合理的です。

Go言語の将来性はあるか?

GoはGoogleが主導する言語であり、Kubernetes・Docker・Terraform等のクラウドインフラツールが採用していることから、少なくともクラウドネイティブ領域では長期的に需要が続く見通しです。Go 1.25以降も開発者体験の改善(ジェネリクスの拡張、イテレータのrange over func等)が継続しており、言語としての進化も止まっていません。

Go言語の弱点は何か?

Goの主な弱点は、GCによるテールレイテンシ、ジェネリクスの制約(型パラメータの表現力がRustやHaskellほど柔軟でない)、エラーハンドリングの冗長さ(if err != nilの繰り返し)です。また、GUIアプリケーションやCPU集約的な数値計算にはあまり向いていません。

PythonやNode.jsからの移行先としてはどちらが適切か?

チーム全体の移行を考える場合はGoが現実的です。Pythonとの構文的な類似性は低いものの、言語仕様のシンプルさから学習コストが低く、チーム全員がGoを書けるようになるまでの期間が短いためです。一方、特定のボトルネック箇所だけを高速化したい場合は、PythonからRustを呼び出す(PyO3を使用)選択肢もあります。

まとめ

GoとRustのWeb API開発における使い分けの要点は、以下の3つに集約されます。

  1. Goは「チームの生産性」を最大化する言語 – 学習コストが低く、コンパイル速度が速く、大規模チームでも一定の品質を維持しやすい
  2. Rustは「実行時の効率」を最大化する言語 – GCなしのメモリ管理、コンパイル時の安全性保証、ゼロコスト抽象化による最小限のオーバーヘッド
  3. 両者は競合ではなく補完関係にある – GoのAPIゲートウェイとRustの計算集約サービスを組み合わせるハイブリッド構成も実践されている

どちらの言語も2026年時点でWeb API開発に十分な成熟度を持っています。プロジェクトのSLA要件・チーム規模・開発スピードの優先度に応じて、適切な言語を選択してください。