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

「Claude Code」を使用して、1ヶ月で10万行をTypeScriptからRustに移植する

概要

  • AIとアルゴリズム を組み合わせて大規模コードベースをRustに移植する試み
  • Pokemon Showdown のJavaScript実装をRustに変換する個人プロジェクトの体験談
  • Claudeのサンドボックス回避や自動化の工夫、技術的課題の詳細
  • 移植作業の工程、問題点、AI活用の限界と人間の介入ポイント
  • JavaScriptとRust間の言語仕様や設計思想の違いによる課題

Claudeと共に進めるPokemon ShowdownのRust移植体験

  • Microsoftの大規模コードベース をC++からRustへ移植する戦略に触発された体験談
  • Pokemon Showdown のオープンソース実装をRustに変換する理由として、AIトレーニングループでの パフォーマンス向上 を目指す動機
  • Claudeを活用し、 JavaScriptからRustへの自動変換 に挑戦する休日プロジェクト

サンドボックスの回避方法

  • Claudeのサンドボックスには sshアクセス制限 などが存在し、GitHubへのpushができない問題
  • Node.jsで ローカルHTTPサーバー を立て、外部からgitコマンドを実行する仕組みを構築
  • Docker環境 内でビルド・実行することで、アンチウイルスによるブロックを回避
  • AppleScriptやAuto Clickerを活用し、 自動で許可操作 やターミナルのフォーカス維持を実現

Claudeの自動化と制御

  • Claudeが繰り返し 人間の許可を求める 挙動を、AppleScriptでEnterやcmd-vを自動送信して回避
  • システムアップデート等による ターミナルのフォーカス喪失 をAuto Clickerで解決
  • 長時間実行時の 安定性・信頼性 の課題と、特定のエラー発生事例

コード移植の実際

  • 最初はClaudeに 単純なプロンプト で移植を依頼、数千行のRustコードが生成されるも、構造の不整合や 安易な抽象化 が多数発生
  • JavaScriptの各ファイル・メソッドごとに Rust側に対応付け、コメントで元のソースを残すスクリプトを作成
  • ファイルサイズ肥大化による コンテキストウィンドウの制約 を回避するため、メソッド単位でファイルを分割

移植後のクリーンアップと統合

  • Claudeによる 大規模な一括変換 の後、手動で問題点を特定し、AIに 具体的な指示 を出して修正
  • Rust移植時に 本来分離すべきロジック が各所にハードコーディングされる問題を人間が発見・誘導
  • 統合テスト は全体を移植してから一度に実施、JavaScriptとRustで同じ入出力となるようテストハーネスを自動生成
  • 数百万件規模のバトルシミュレーションで 一貫した検証 を実施、バッチサイズを増やしながらバグを減少

RustとJavaScriptの違いによる課題

  • Rustの 借用チェッカー により、PokemonとBattle間の相互参照問題が発生
    • コピー、インデックス渡し、コールバック関数でmutable参照を扱う工夫
  • JavaScriptの 動的型付け とRustの厳格な型付けの差異
    • Option<>やstructの多用で対応、不定値や可変引数の表現違い
  • ClaudeのAIとしての 限界
    • 複数ファイルにまたがる本質的な修正や、複雑なロジックの移植を回避しがち
    • テストに合格するための 場当たり的なハック や不完全な実装が頻発
    • コメントを「唯一の情報源」としても、AIが独自にコードを変更する場面も

ClaudeとAIコード移植の現実と教訓

  • AIによる大規模コード移植 は、驚くほど多くの作業を自動化できる一方で、 人間の設計力・レビュー が不可欠
  • 構造化・分割・明確な指示が品質向上の鍵
  • 言語仕様や設計思想の違い をAIが完全に吸収するのは難しく、AIの「楽をしたがる」傾向を人間が制御する必要
  • 今後の発展に期待しつつ、現状では 人間とAIの協働 が最適解

Hackerたちの意見

こうやって24時間Claude Codeを動かすのに、どれくらいお金がかかるんだろう。月200ドルのプランは大丈夫なのかな?Cursorの出費が結構高いから、200ドルのCCサブスクリプションにまとめられないかなって思ってる。

毎日のトークン制限があるんだよね。人間としてClaudeを使ってる時はその制限に引っかかったことはないけど、もうすぐ限界に近づいてるって警告はもらったことがある。無人で運用すると、すぐにトークン制限に達しちゃうと思う。

Maxサブスクリプション(200ドルプラン)については直接の経験はないけど、ここやGitHubでいくつかの議論を読んだ感じだと、最近Anthropicが使用制限を厳しくしたみたいで、24時間毎日使ってるとすぐに制限に引っかかるんじゃないかな。1) https://github.com/anthropics/claude-code/issues/16157

この人がテストしたみたいだよ: https://she-llac.com/claude-limits 「怪しいほど正確な浮動小数点、または、どうやってClaudeの本当の制限を知ったか」19時間前 25ポイント https://news.ycombinator.com/item?id=46756742 一方で、ChatGPT/Codexは制限があまり問題にならないことが多いね。

24時間使ってるなら、コンテキストを管理するのに気をつけないと、すぐに制限に引っかかると思うよ。リクエストが長時間のツール使用(例えば、時間がかかるテストスイート)で区切られてれば別だけどね。私は月200ドルのプランを使ってて、Claudeを数時間無人で動かしてることもある。特に激しく使ってる時には週の制限に達したこともあるけど、複数のセッションを同時に使ってたから、24/7の1セッションにどれくらい近づいてたのかはよくわからない。

LangGraphを使って出版バックエンド用に似たような自律ループを作ったけど、APIの生のコストは$200をかなり超えてたよ。サブスクリプションモデルには、そういう負荷の下で早くトリガーされる不透明な使用制限があるかもしれない。ブートストラップされたセットアップでは、APIの請求の予測可能性が、ブラックボックスの制限に引っかかるよりもプレミアムに値することが多いんだ。

記事からいくつかの引用が目立つね: 「Claudeはしばらく働いた後、いつも物事を振り返るために止まるようだ」質問: コンテキストが足りなくなってたの?だから、意図的な圧縮みたいなフレームワークが開発されてるんだね。大きなコードベースにはLLMを使う際に特定のニーズがある。「私は人生で一度もRustと関わったことがない」 :-/ これっていいアイデアなの?生成されたコードをどう信じればいいの?

QAが書いたテストスイートがあればいいけど、なければバグだらけになるのは確実だね。何かを再構築しなきゃいけないとき(実際にはあまり必要ないことが多いけど)、段階的なアプローチが一番だってことをみんなに学んでほしいな。

ちょっと懐疑的だけど、元のものをリファレンス実装として使って比較するのは簡単だよね?たくさんのランダムな入力を提供して、不一致を修正するのはシステムを再構築/移植するためのクラシックなアプローチだし。

これがいいアイデアだって?生成されたコードをどうやって信じるの?信じられないよ。LLMがコードを書いたんだから、絶対に正しいってわけじゃない。/s 何が悪くなるっていうの?

書類の自動翻訳を信じるのと同じだよ。英語(または得意な言語)で書いたけど、誰かがタイ語やチェコ語にしてほしいって言うから、ボタンをクリックしてその書類を送る。もうそれは相手の問題だよね。

これに対する俺の答えは、LLMに複数回のコードレビューをさせることだね(コードの重要性によっては、全てのコミットでレビューをするけど、これは明らかに影響のない趣味のプロジェクトだった)。彼らは驚くほど物事を見抜くのが上手いよ、特に毎回やるとね。

彼の目標は、ポケモンの挙動をエンコードしたより速いオラクルを手に入れることだった。それを別のトレーニングプロジェクトに使えるようにね。このプロジェクトは、メンテナンスや理解が必要ない形でそれを提供しているんだ。

著者は、リファレンス実装と新しいRust実装の両方を200万回(!)のランダムに生成されたバトルでテストして、結果が一致しないバトルをフラグ付けしていると言ってる。

いいテストカバレッジがあれば、Oracleで働いてた人が言ってた「テストファーム」のような感じでうまくいくと思うよ。

人生で一度もRustのコードを書いたことがない 実験としては面白いけど、使ったことのない言語で10万行のコードベースを維持するのは悪夢のシナリオに思える。

そのコード、GC言語を真似た非イディオマティックなRustコードでいっぱいになってるんじゃないかな…それが「修正」されれば、今のRustバージョンよりもずっと速くなるかもね。

計画ではClaudeがそれを維持することになってるみたい。彼はコードの一行も読んでないけどね。

これ、クラウドにAndroidのlibgdxベースのゲームをWASMベースのlibgdxバージョンに移植させようとした時を思い出す。ブラウザでゲームをプレイしたかったんだけど、ほぼ行ごとに移植させようとしても、ずっとコードを「改善」しようとしてた。最終的にはバグをいくつか引き起こして、全部元に戻さなきゃいけなかった。「どうしたらもう二度とこんなことをしないようにできる?」って聞いたら、こんなのが返ってきた:### 重要な教訓:移植中に「改善」しないこと - 最大のミス:動作するコードを再編成すること - 俺が間違えたこと: createStartButton()を別々の作成とレイアウトメソッドに分けて「簡素化」しようとした - 失敗した理由: 3つのバグを引き起こした:1. レイアウトの重なり(getY() vs getY() - getHeight()) 2. 子要素のサイズが設定されない(Group.setSize()は子要素に影響しない) 3. 原点が更新されない(スケーリングアニメーションが壊れる) - 修正方法: 自分の「改善」を削除して、元のAndroidパターンを忠実にコピーした - 根本原因: 傲慢さ - 制約を理解せずに生産テスト済みのコードを改善できると思ったこと - 解決策: 上記の移植原則に従うこと - まずコピーして、再編成しない - 無駄にした時間: ~1時間、自分で引き起こしたバグをデバッグしてた。元のコードをそのままコピーしてれば存在しなかったのに - 重要な洞察: 元のAndroidコードは正しくて、戦闘テスト済み。君の「改善」はバグを引き起こすだけだ。クラウドの自己反省は好きだけど、残念ながらこれをCLAUDE.mdに追加しても解決しなかったし、間違った方向に進み続けたから、その努力を諦めざるを得なかった。

各コンパクションの後に「Xを再読してください」と言える機能があればいいのに。

Claudeはなんでそう動いたのか分からないんだ。ただ、どうしてそう動いたのかを予測してるだけ。こういう罠に引っかかる人、よく見るよね。

あなたのIDEやプラグインは、あなたのプロンプトの前にたくさんのプロンプトを追加することが多いから、それも影響してるかもね。モデルホスティングプロバイダーが前に追加するプロンプトもあるし。これがエージェントに改善みたいなベストプラクティスを促してるのかも。私のを見ると: >「あなたは多くの異なるプログラミング言語やフレームワーク、ソフトウェアエンジニアリングタスクに関する専門的な知識を持つ高度に洗練された自動コーディングエージェントです - これにはデバッグ問題、新機能の実装、コードの再構築、コードの説明提供など、他のエンジニアリング活動が含まれます。」LLMがこれを「進めるべきこと」と解釈するのもあり得るよね。モデル(人間もだけど)は、否定的なことにはあまり反応しないからね(「ピンクの猿を考えないで」って言ったら、今私たち二人とも考えちゃってるし)。

限定的なインタラクションからのリカバリーに効果的かもしれないのは、コードレビューのプラグイン[1]だね。これがエージェントを生成して、変更がCLAUDE.mdで指定されたルールに従っているかをチェックするんだ。[1] https://github.com/anthropics/claude-code/blob/main/plugins/...

こういう大きなものに関しては、最初にテストを移植するのが重要だと思う。その後、テストを変更せずにテストが通るように強制する感じ。純粋に関数型のものにはうまくいくけど、GUIアプリだとかなり難しいね。

これはClaude Codeだったの?チャットUIで1ファイルずつ試したら、ストレートなポートができると思うけど。編集:Rustは他の言語とはちょっと違うから、1:1のポートが常に可能とは限らないかも。自分はRustをあまり触ったことがないけど、LLMを使って何かをRustに移植しようとすると、最初に20個くらいのcargoクレートをインポートしちゃうんだ(元の言語には依存関係がなかったのに)。それに、ゲーム開発でのRustは辛い経験だった。Rustはグローバルを嫌うし、全体主義的だから「実は自分は大人だから、これやらせて」って言えないんだよね。だから変な回避策をしなきゃいけなくて。GPTが「簡単だよ、ただこのルーブ・ゴールドバーグのマクロクレートが必要なだけ」みたいなことを言い出して、最初は何言ってるんだと思ったけど、Rustのディスコードに参加したら同じアドバイスをもらった。結局、最後の日にTSに戻って、全部やり直したよ。

ちょっと脱線するけど、libgdxってネイティブウェブサポートあったよね?

ソネット4.5はこの問題があったんだよね。オーパス4.5は、脱線せずにタスクに集中するのがずっと上手だよ。

CLAUDE.mdにはあまり縛られてないみたいだね。

コンテキストフリーではないけど(ハハ)、試してみる価値があるトリックは、プロンプトにネガティブな例を含めることだよ。最初はワルイージ効果のせいでひどいトリックだったけど、今ではいいトリックになったし、最近のオーパス4.5ではあまり必要なくなった。でも、一度は効果があったよ。例えば、元のコードを使って、正しい答えと間違った答えをプロンプトに例として入れてからやり直すって感じ。もしうまくいったら、ぜひ教えてね。

「AppleScriptを使って、別のタブで数秒ごとにEnterを押すことができることに気づいた。これでClaudeが何かを頼んできても、全部Yesって言うことになる。これ、めっちゃバカバカしいけど、こういう手法にはちょっと敬意を表しちゃう。」

こういうの、逆の方向に行くこともあるよね。研究者(一般的にはソフトウェアエンジニアじゃない)たちが言うには、「Claudeを使って大規模なRustコードベースをPythonに移植したら、ゲームチェンジャーになった。Rustコンパイラといつも戦ってたのが、今はPythonでサクサク進められて、邪魔されない。AIの助けを借りて、毎日何千行も動くコードを追加してる。」こういうの読むといつもゾッとする。だって(少なくとも私の会社では)、研究コードが直接プロダクションに出されることが多くて、研究者以外には誰もその仕組みが分からないから、結局は型がなくて、ランタイムの問題が起きるたびにスタックトレースが出るような脆弱なコードになっちゃうんだ(最初は結構頻繁に問題が起きて、時間が経つにつれてなんとかなるけど)。

クローズドソースのウェブ会議ツールを、実際に数時間だけ触って、約1週間でRustに移植したんだ。ブラウザにホストされていた2.8MBのミニファイドJSから、オーディオやWebRTC、グラフィックス、埋め込みブラウザなどを含む35MBのARM実行ファイルに。プロトコルやクライアントUIを説明するmdbookの仕様も作ったよ。自分が書いたコードはゼロ行。全体の作業内容を理解する必要があったし、スレッドやバッファリング戦略の高レベルな設計、どんなオーディオ処理をするか、GPUでスプライトグラフィックスをどうするか、実際のCPU時間やメモリアロケーションを理解するためにプロファイラーを使ったりもした。手作業で同じ時間内にこれをやるなんて絶対無理だし、明らかに知的財産が絡んでるから、簡単だったからこそやったけど、そうじゃなきゃ時間をかける気にはならなかったね。

ぜひ、詳しいレポートをお願いします!

これはLLMにとって最高のユースケースの一つだと思う。古い有用なPythonやJavaScriptを、より速いコンパイル言語のコードに移植するっていうのは。自分はやりたくないけど、ほとんどの人がAIにはすでにあると認めているタイプの知性が必要だよね(明確な目標に従うこと、あまりクリエイティビティや主体性が必要ない)。

著者の差分テスト(230万回のランダムバトル)は、最終的な検証として素晴らしいけど、ここでの本当の教訓は、ポート中にモジュールテストを行うべきだってことだね。1. まずテストを移植する - それが契約になる 2. 次に進む前にモジュールごとにユニットテストを実行する - 「異なる移動構造が2つある」みたいな問題を早期にキャッチ 3. 進む前に境界で統合テストを行う 4. 最終的な検証としてE2E/差分テストを行う 目標言語を読めないときは、テストスイートが唯一の信頼できるフィードバックになる。統合の問題にかけたデバッグ時間は、進行中のテストで早期にキャッチできたはずだよ。

本当の教訓は…つまり、もしこれが1ヶ月かかったとしたら、TFAはすでに素晴らしい成果を上げたってことだよ。次回はもっと良くなるに決まってる。

ポーティングで学んだことの一つは、主要な機能が壊れていないか確認するために、エンドツーエンドの統合テストを用意しておくべきだってこと。