世界を動かす技術を、日本語で。

GoからRustへの移行

2026年5月25日原文(corrode.dev)

概要

  • GoからRustへの移行 は、速度や型の有無よりも 正しさ保証開発体験の違い が焦点
  • 本ガイドは バックエンド開発 に特化し、GoとRustの 実務的な比較 を提供
  • Goの強みと限界、Rust移行で得られるメリットを 具体例 とともに解説
  • ツールチェーンや型システム、エラーハンドリングなど 主要な違い を整理
  • Go開発者向け に、Rust導入の判断材料や 移行時の注意点 を明確化

GoからRustへの移行ガイド:バックエンド開発者のための実践比較

  • GoからRustへの移行 は、性能や型システムの単純な比較ではなく、 正しさ保証ランタイム特性開発者体験 が主な論点
  • 本ガイドは バックエンドサービス に焦点を当て、 Goの得意分野 (小さな静的バイナリ、ネットワーク中心の標準ライブラリ、HTTP/gRPC/DBエコシステム)と Rustの特徴 を比較
  • CLIツールや組み込み、ゲームエンジンには本ガイドの内容が完全には適用されない場合もある旨を注意喚起
  • 筆者は Goの設計思想に批判的 だが、Goの実用性や普及度を認め、客観的な比較を重視
  • Rustコンサルタント としての立場を明示しつつ、 両言語での実務経験 をもとに現場目線で解説

GoとRustのツールチェーン比較

  • Go は「バッテリー同梱」思想で、ビルド・テスト・フォーマット・依存管理などの 一貫したツールチェーン を提供
  • Rust も同様に cargo を中心とした豊富な標準ツールが特徴
    • go.mod / go.sum → Cargo.toml / Cargo.lock:プロジェクト管理
    • go build / go run → cargo build / cargo run:ビルド・実行
    • go test ./... → cargo test:テスト
    • go vet / golangci-lint → cargo clippy:静的解析
    • gofmt → cargo fmt:自動フォーマット
    • その他、ドキュメント生成や脆弱性スキャンも標準対応
  • Goはサードパーティツール (golangci-lint, mockgen等)への依存が多め、Rustは 公式ツールで網羅度が高い
  • コードスタイルの統一 (gofmt/rustfmt)は、議論の排除と生産性向上に寄与

GoとRustの主な違い

| 項目 | Go | Rust | |---------------------|---------------------------------------|----------------------------------------| | 安定版リリース | 2012 | 2015 | | 型システム | 静的・構造的・1.18以降ジェネリクス | 静的・名義的・ジェネリクス+トレイト+ライフタイム | | メモリ管理 | ガベージコレクション(低遅延) | 所有権・借用、GCなし | | Null安全 | nil多用 | nullなし、Option<T>で型安全 | | エラーハンドリング | errorインターフェース、if err != nil | Result<T, E>、?演算子、網羅的マッチ | | 並行処理 | goroutine+channel(CSP) | async/await、tokio、スレッド | | キャンセル | context.Context(慣習、型安全でない) | CancellationToken等、型安全 | | データ競合検出 | -race(実行時、確率的) | Send/Syncでコンパイル時に検出 | | コンパイル速度 | 非常に速い | 遅い(特にクリーンビルド) | | ランタイム | 約2MBのGoランタイム+GC | libcのみ、または完全静的リンク可能 | | バイナリサイズ | 小~中(数MB) | 同等、小型化オプションあり | | 学習曲線 | 緩やか | 急勾配 | | エコシステム規模 | 75万+モジュール | 25万+クレート |

  • 両言語とも単一バイナリ配布・強力な並行処理サポート が特徴
  • 違いは 型システムによる保証の範囲ランタイム制御の自由度

型システムによる保証の違い

  • Goは慣習やツール (go vet, errcheck, -race等)で安全性を担保
  • Rustは型システム (Option, Result, 所有権, Send/Sync等)で コンパイル時に強制
  • 例:Mutex<T>は型としてロック取得を強制し、ロック忘れのバグが発生しない設計

Go開発者がRustを検討する理由

  • 速度不足 ではなく、 エラーハンドリングの冗長さnilポインタによるセグフォ型システムの限界 (長らくジェネリクス未対応、enumやtraitの不在)への不満が主
  • 標準ライブラリの穴 (Set型未実装等)も不満点
nilによるパニックの事例
  • Goではnilチェック漏れによる 本番クラッシュ が発生しやすい
  • RustのOption<T>必ずNoneケースを考慮 させるため、カテゴリごとバグを撲滅可能
データ競合(Data Race)の検出
  • Goの-raceは 実行時検出 で、テストで発生しない競合は本番で露見
  • Rustは Send/Syncトレイトコンパイル時に共有可否を判定、競合は型エラーとして排除
エラーハンドリングの比較
  • Go:if err != nilによる 明示的・冗長なエラー処理、fmt.Errorfによるラッピングは慣習頼り
  • Rust:Result<T, E>と?演算子、#[from]属性で 自動的なエラー伝播・ラッピング、enumで網羅的なエラー型定義
  • 新たなエラー型追加時は全呼び出し箇所をコンパイラが指摘、見落としゼロ
ジェネリクスの違い
  • Goは1.18以降ジェネリクス対応だが、 制約や実装上の制限 あり
  • Rustは トレイト境界や型パラメータ を活用し、 より柔軟かつ型安全 な抽象化が可能

GoとRustのパターン対応・移行判断

  • Goの典型パターン (interface, goroutine, channel等)はRustでどう表現するかを順次学ぶ必要
  • 移行コスト は小さくないが、 安全性・保守性・型システムによる恩恵 が大きい分野ではRustが有力選択肢
  • 既存Goサービスの段階的移行併用運用 も可能

まとめ:どんな時にRustへ移行すべきか

  • Goのシンプルさ・生産性 が活きる領域ではGo継続が妥当
  • 正しさ保証・並行性・型安全性 が最重要な場合、 Rust移行は十分な価値
  • 両言語の長所短所を冷静に理解 し、 現場の要件に応じて選択判断 が必要

Hackerたちの意見

これ、ちょっとありきたりに聞こえるかもしれないけど、Rustの一番の不満はパッケージ管理の状況なんだよね。これは完全に開発者の考え方のせいだと思う。Rustのエルゴノミクスは好きなんだけど(データ型への関数的アプローチが美しいよね)、今、RustとGoで二つのプロジェクトを並行して進めてるんだ。依存関係のツリーが全然違ってて、Goのプロジェクトはほとんど標準ライブラリでカバーされてるのに、Rustのプロジェクトはrusqlite(sqlite)、clap(cli)、ratatui(tui)、tauri(gui)を要求してるのに、依存関係が400以上になっちゃってる。特にtauriが一番の問題なんだけど、これがなくても100近くになるのはマジでクレイジーだよ。もし、ちゃんとメンテされてるRustのクレートの代替品があれば、もっと気が楽になるんだけど。システムを壊さないようにしたいだけなんだよね。Rust-webの人たちは、Cargoをnpmみたいにしたがってるみたいだし。

面白いね。Goにはあまり詳しくないんだけど、Tauriに相当するものはGoの標準ライブラリにあるの?ユーザーケースに応じて、フロントエンドはGoで、バックエンドだけRustでやるのはアリかな?

標準ライブラリは、良いアイデアが消えていく場所だよね。それから httplib3、httplib4 が続く。つまり、私は Rust のアプローチが断然好き。標準ライブラリに依存するか他の依存関係に頼るかは、私にとってあまり重要じゃない。結局、依存関係なんだから。人々は、標準ライブラリだからって品質が良いとかメンテナンスが行き届いていると思いがちだけど、これは別の話。結局はリソース次第。確かに、標準ライブラリはリソースが多いかもしれないけど、肥大化してメンテナンスが難しくなることもあるしね…

必要な機能を正確に提供する他のパッケージをいくつもインポートするのが、必要な機能の一部しか提供しない大きな標準ライブラリを使うよりも悪い理由って何なの?

パッケージ管理はほぼすべての言語や技術の厄介な問題だよ。誰も「解決」してないし、これからも解決策は出てこないと思う(でも、何事も可能性はゼロじゃないよね?)。Go では、ライブラリの開発者がセマンティックバージョニングを正確に守ることに頼ってるし、バージョンを「固定」できないのが個人的に不満。いくつかの回避策はあるけど、SHA を使って疑似バージョンを提供したり、ベンダリング(既知の依存関係のキャッシュで、キャッシュ管理の問題もある)をしたりすること。週末に Python を仮想環境で使わなきゃいけなかった不運があったけど、うまくいかなかったし、Python から移行した理由を思い出させてくれた。Perl(cpan)、Java(maven, gradle)、Ruby(gems)、Go(dep, glide, vgo, modules)、Rust(cargo)、Node(npm, yarn など)、OS も Redhat(yum, rpm など)、Debian(apt)、Ubuntu(snap - なんでこれが必要なの??)などなど。

Rustのライブラリは多くのクレートから成り立っていて、それが依存関係グラフに影響を与えることに注意してね。これが実際よりも依存関係の数が多く見える原因なんだ。別々のクレートは同じメンテナーが管理していて、しばしば同じ上流のgitリポジトリに属しているからね。とはいえ、一般的な意見には同意するよ。Rustには、半ばメンテナンスが行き届いてない0.xバージョンのクレートもたくさんあって、代替手段がないことが多いから。

rusqlite(sqlite)、clap(cli)、ratatui(tui)、tauri(gui) これに匹敵する標準ライブラリを持つ言語って、Java以外にあるの?それに、Tauri自体が14のクレートで構成されていて、それぞれがビルドツリーに表示されることも忘れないでね。 https://github.com/tauri-apps/tauri/blob/dev/Cargo.toml そしてRatatuiは6つだよ: https://github.com/ratatui/ratatui/blob/main/Cargo.toml

ベンチマークをやる前はRustが好きだったけど、ほとんどのLLMがRustで書くのとGoで書くのの効果の差が、思ったより大きかったな(特にエージェント的なハーネスで、初期の環境問題を修正できるところ)。それを見てから、かなりのRustの伝道者になっちゃった。既存のコードベースから呼び出すバッチ処理ツールをRustで書くのは成功してるけど、まだフルプロダクションの移行には挑戦してない...。記事で指摘されてるGoの問題、特にnilの扱いについては、Codexでの徹底したコードレビューでどんどん解決されてきてると思う。最初から問題がないのが一番だけど、こういうセキュリティバグは、初期設計や実行にかけた努力と同じくらい、コードをレビューして理解する努力をする開発者にはオプションになりつつあるね。言語データは https://gertlabs.com/rankings?mode=agentic_coding

RustのLLMに対する弱点はコンパイル時間だね。LLMはコードを書くのが早いから、比較的コンパイルを待つ時間が人間よりも多くなる。だから、適度なサイズのプロジェクト(例えば、10万行以上)では、Rustのコンパイルが約10倍遅いことがボトルネックになってくる。重要なインフラを作るなら、そのコストを払うのは意味があるけど、内部サービスでインターネットに公開されていないものを書くなら、開発のスピードがもっと重要かもしれない。(遅いコンパイルが人間の開発速度にも影響すると思うけど、なぜか開発者はこれを定量化しようとすることがめったにないんだよね。)

詳細なコンパイラーエラーと強力な型システムのおかげで、変更 -> コンパイル -> 変更のループがエージェントにとって簡単に扱えるんだ。Rustはユーザーを強く制約するレールを提供してる。Codexはいつも何かをコンパイルさせることができるんだ。ただ、時にはイディオム的なアプローチが無理なときに失敗すべきだと思う…その代わりに、コンパイルできてリクエストを満たすような愚かな実装をすることがあるんだよね。

CやC++、PythonからRustに移行するのは色々な理由で理解できるけど、ウェブのバックエンド作業にはGoが合ってると思う。ほとんどRustで書いてるけど、最後にRustでウェブサーバー側のことをやったとき、Goを使っておけばよかったなって思った。OPがGoのエラー構文の冗長さを指摘してるけど、確かにいいポイントだね。Rustも最初は同じ問題があって、「?」構文を追加したんだ。これでエラーが出たときはエラー値を返すだけになる。Goのエラー処理はほとんどがそのまま書かれてる。Rustには統一されたエラータイプがないから、エラー処理が面倒なんだよね。Rustには三つの主要なエラーシステム(io::Error、thiserror、anyhow)があって、呼び出しのチェーンを通して渡すのが大変なんだ。(新しい言語には欠けてることが多くて、後から追加するのが面倒なことがある。ほぼ同じだけど互換性のないバージョンが出てくるから。定数型、ブール型、エラー型、多次元配列型、サイズ2、3、4のベクトルや行列型とその通常の操作。これらが早い段階で標準化されないと、プログラムは同じものの複数の表現に悩まされることになる。エラー処理を除けば、これらの問題はウェブ開発にはあまり影響しないけど、数値処理やグラフィックス、モデリングには大きな痛手だね。標準操作が数値の配列に適用されるから。)Goにはウェブサービスに対する二つの主な利点がある。まず、OPが指摘してるgoroutines。次に、OPがあまり言及してないライブラリ。Goにはウェブサービスに必要なほとんどのライブラリがあって、Googleが内部で使ってるものなんだ。だから、非常に使われている環境でも生き残ってる。マイナーなケースでもよく使われてる。これはRustのクレートには当てはまらなくて、成熟度が低くて、正式なQAサポートもないことが多い。

Rust には三つのエラーシステムはない。ひとつだけ、Error トレイトがある。io::Error はその実装の一つで、特別なものじゃない。thiserror で定義されたエラーもこれを実装してる。「Anyhow」は、関数が返すエラーの型を指定する API コントラクトを書くのが面倒なときに「何らかのエラー」と便利に言えるようにしてくれる。

Hacker Newsで議論の続きを見る