Corrode Rust Consulting の記事「Migrating from Go to Rust」は、Go から Rust へ移るときの論点をかなり正直に整理したガイドです。
面白いのは、「RustはGoより速いの?」みたいな単純な話ではないと最初に釘を刺しているところだと思います。
著者の主張をざっくり言うと、Go も Rust もサーバー開発には強い。
ただし Rust は、コンパイラがもっと多くのミスを事前に防いでくれる。その代わり、学習コストや書き方の制約は増える。
つまり、移行の価値は「性能アップ」よりも 正しさ・安全性・開発体験の改善 にある、という話です。
この記事はかなり backend-focused です。
つまり、HTTP サーバー、gRPC、DB、API サービスみたいな領域が主戦場。
これはかなり大事な前提です。
Go はまさにこの領域で強いので、Rust への移行を考えるチームも、だいたいここにいます。
CLI、組み込み、ゲームエンジンでも参考にはなるけれど、記事として一番役に立つのはバックエンド開発者だ、という立て付けですね。
この割り切りは誠実だと思います。
「Rustは万能です」と言い出さないのがいい。移行の話って、つい理想論に寄りがちなので。
記事の書き手は最初からこう言っています。
自分は Go があまり好きではない と。
かなり強い言い方ですが、理由はちゃんと説明されています。
nil があちこちに出るただし、ここで大事なのは「嫌いだからダメ」とは言っていないことです。
実際、Go は広く使われているし、仕事で困らず動いている人もたくさんいる。
そこを認めたうえで、Rust への移行を考える人に向けて、比較の軸を整理しているわけです。
このバランス感覚は、私はかなり好感を持ちました。
技術記事って、推し言語の布教ポスターになりがちなので、こういう「自分の偏りも開示する」書き方は信頼しやすいです。
両方とも以下の共通点があります。
でも、コンパイラがどこまで保証してくれるか が大きく違います。
if err != nil で都度確認するSend / Sync などでスレッド安全性を型で見るResult<T, E> でエラーを型として表す要するに、
Go は「動かしながら安全を確認する」寄り、
Rust は「コンパイル時に安全を詰める」寄り です。
個人的には、この違いはかなり本質的だと思います。
Rust の強みは「機能が多い」ことより、ミスを起こしにくい設計が最初から言語に埋め込まれていることなんですよね。
borrow checker は、何がうれしいのかRust でよく話題になるのが borrow checker(借用チェッカー)です。
これは、ざっくり言うと 「この値を誰が、いつ、どう使ってよいか」をコンパイラが厳しくチェックする仕組み です。
一見すると面倒です。
でも記事では、ここをかなりうまく説明しています。
たとえば Mutex<T>。
Go だと「このデータを触る前に lock を取るのを忘れないでね」という注意が必要です。
Rust だと、そもそも lock を取らないと中身に触れないようになっている。
lock() を呼ぶと guard が返ってきて、その guard を持っている間だけアクセスできる。
guard が drop されたら自動で unlock される。
つまり Rust は、
「うっかり忘れる」余地を型で潰しているんです。
これは地味ですが、めちゃくちゃ強い。
人間の注意力に頼る箇所が減るので、保守が楽になります。
もちろん最初は窮屈です。でも、慣れると「これ、むしろ脳のメモリを節約してくれるな」と感じる場面が増えるはずです。
記事が面白いのは、Go 開発者が Rust を検討する理由を「Go が遅いから」とは見ていないところです。
むしろ多くの場合、次のような細かいストレスが積み重なっているそうです。
nil による事故が怖いこの感覚はすごくわかります。
大事故より、毎日の小さいイライラのほうが移行理由として強いことってありますよね。
「なんとなく書きにくい」「なんとなく怖い」が積もると、別の言語に目が向く。かなり自然です。
Go でよくあるのが nil 問題です。
存在しない値をうっかり触ってしまい、実行時に panic するやつです。
記事では、たとえば「検索結果が見つからなかったときに nil が返る」ケースを挙げています。
Go では、呼び出し側が user != nil を忘れると、あとで user.Account.Notify() のような箇所で落ちることがある。
一方 Rust は Option<T> を使うので、
値があるかないかを明示的に扱わないといけない。
None の可能性を無視して値を取り出す、という雑なことはできません。
これはかなり大きいです。
「見つからないかもしれない」をコードの形で強制できるので、事故の種類が減る。
著者が「pager-duty incidents が減る」と冗談っぽく書いているのも納得です。夜中に起こされる事故は、できるだけ減らしたいですからね。
Go では go test -race が強力な武器です。
でもこれは runtime detector なので、実際にその競合がテストで起きないと見つからないという弱点があります。
たとえば複数 goroutine から map をロックなしで更新するようなコードは、Go ではコンパイルできます。
そして本番負荷で突然壊れることがある。
Rust では、共有可変状態をスレッド間で扱うには Send / Sync などの条件を満たす必要があります。
単純な HashMap をそのまま共有しようとすると、そもそもコンパイルが通らないことがある。
これは、かなり痛快です。
「壊れるコードを実行前に止める」わけですから。
もちろん Rust でも何でも安全、というわけではありませんが、少なくとも data race のクラスはかなり前倒しで潰せます。
Go の if err != nil { return err } は、最初はシンプルです。
でも長く使うと、次のような不満が出ると記事は指摘しています。
fmt.Errorf("...: %w", err) を忘れやすいGo 側にも反論はあって、lint や errcheck がかなり助けになる、というのはその通りです。
記事もそこは認めています。
それでも Rust の Result<T, E> は、
「失敗する可能性」を関数の型として明示する のが強い。
? 演算子でエラーを上に流せるので、書き味も意外と悪くない。
著者も「? は explicit だ」と見ています。
このへんは好みが分かれますが、私は Rust のほうが「読み返したときに安心」だと思います。
Go の明示的な if err != nil は確かに読みやすい。
でも Rust の ? も、慣れると「ここは失敗を上に任せている」と一目でわかるので、十分に明快です。
この記事は、Rust への全面移行を煽っていません。
むしろ、Go が向いている場面もある という前提がかなり強いです。
Go を続ける価値があるのは、たとえばこういう場合だと思います。
一方、Rust を検討したくなるのは、たとえばこういう状況でしょう。
個人的には、Rust の価値は「最高速度」よりも、大規模運用での安心感 にあると思います。
Go はとても良い言語ですが、「気をつけていれば大丈夫」を積み上げる文化に寄りやすい。
Rust は「気をつけなくても壊しにくい」方向に寄っている。
この違いは、長く使うほど効いてくるはずです。
記事の最後では、Go サービスを Rust に移すときは段階的な移行が重要だと示唆しています。
これはかなり実務的です。
いきなり全部を書き直すのは、だいたいしんどいです。
現実には、周辺の小さいサービスや、新規機能、一部の性能クリティカルな部分から Rust にしていくほうが安全でしょう。
この「全部リライトしない」という姿勢は大事です。
技術移行で失敗する典型は、たいてい「理想の完成形」を先に決めすぎることだと思います。
Rust は強いけれど、移行コストは確実にある。だからこそ、少しずつが正解になりやすい。
この記事を読んで感じるのは、Go と Rust を「どっちが勝ちか」で見るより、
何をどこまでコンパイラに任せたいか で選ぶのが本質だ、ということです。
Go は軽快で、学びやすくて、バックエンドにとても強い。
Rust は厳しいけれど、その厳しさが安全性と保守性に直結する。
なので、Go から Rust への移行は「性能が足りないから」ではなく、
事故を減らしたい、設計の曖昧さを減らしたい、長期運用を楽にしたい という欲求から始まることが多い。
この見立てはかなり現実的で、私はとても納得感がありました。
Rust はたしかに学習コストが高いです。
でも、そのコストを払う価値がある現場も確かにある。
記事はその境界線を、かなり誠実に描いていると思います。