8月21日、rust-analyzerの主要開発者として知られるAlexey Kladov(matklad)が「Rust Glancer」と題した記事を公開した。RAMをrust-analyzerの100分の1に抑えて動作すると主張するLSPサーバー「Rust Glancer」への技術的所感を綴った内容で、rust-analyzer自身の設計判断を率直に棚卸しするものだ。
もともとはlobste.rsのコメントとして書き始めた内容だったが、長くなったためブログ記事に昇格させたという。rust-analyzerを作った本人が「本当はこう設計すべきだった」と公言した内容だけに、Rustコミュニティで広く注目を集めている。
Rust Glancerとは何者か
Rust Glancerは、従来のrust-analyzerとは異なるアプローチでRustのLSPサーバーを実装するプロジェクトだ。rust-analyzerがすべてのコードをインクリメンタル計算フレームワークで処理するのに対し、Rust Glancerは依存クレートのメタデータを軽量に扱うことでRAMを大幅に削減している。matkladはこのアプローチに賛辞を送りつつ、「自分たちがやるべきだったことをやっている」と率直に述べている。
「半分しか描かれていない馬」としてのrust-analyzer
matkladはrust-analyzerを「頭半分しか描かれていない馬」のミーム(「完成しているようで実は中途半端」を揶揄するインターネットミーム)に例える。
理想のアーキテクチャとして挙げるのが、IntelliJ IDEAが採用するPSI(Program Structure Interface)だ。PSIはAST(抽象構文木)に型解決を加えたインターフェースで、バックエンドを複数差し替えられる設計になっている。IntelliJでは実際に3層が使い分けられている:
- エディタで開いているファイル:コンクリート構文木(CST)をフル展開して処理
- プロジェクト内のそれ以外のファイル:「Stub Tree」と呼ばれる、外部から見える宣言だけを圧縮したオンディスク表現を使用。ユーザーが該当ファイルに移動した瞬間、透過的にCSTへ切り替わる
- 依存ライブラリ:コンパイル済みの
.classファイルを直接参照。ナビゲートするとIDEがその場でデコンパイルする
rust-analyzerは現状、6666個の依存クレートも含めてすべてをSalsa(クエリ結果をキャッシュしながら差分再計算するインクリメンタル計算フレームワーク。rust-analyzerのコア基盤として採用されている)で処理している。「ユーザーがまだ触ったことのない依存クレートにSalsaを使うのは無駄だ」というのがmatkladの主張だ。
本当にやるべきだったこと:.rmetaファイルの活用
matkladの処方箋は明快だ。Rustコンパイラが生成する**.rmetaファイル**(クレートの型情報・公開APIなどを格納したバイナリ形式のメタデータ)をrust-analyzerが直接読むべきだった、というものだ。ユーザーが~/.cargo/registry/srcを開いて実際にソースを触り始めたときだけ、Salsaに切り替える。
この方針はかつて計画されており、設計メモも存在する。そこには「rmeta-transparent」という項目があり、APIがコンパイル済み.rmetaファイルを入力として受け付けるべきだと明記されている。しかし実装は完成しなかった。
matkladはこれを「lowest-hanging watermelon(最も手が届きやすいスイカ)」と表現している。通常の「lowest-hanging fruit(最も手が届きやすい果実)」より一回り大きな成果を表す言い回しで、それだけ効果が大きいのに手をつけなかったという自戒が込められている。世界を二分する設計——インクリメンタルで細粒度な「今触っているコードのレイヤー」と、ほぼ読み取り専用でオンディスク・コンパクトな「依存ライブラリのレイヤー」——こそが正解だった、と。
その他の技術的な指摘
rowanについて:rust-analyzerが使う構文木ライブラリrowanは、インクリメンタルパースやDOMスタイルのリファクタリングには適している。ただしそれは1%のユースケースにすぎず、99%の用途では「配列のリスト」のようなシンプルな構造が正解だとmatkladは述べている。自作ライブラリを「ゴミだ :P」と切り捨てるあたりに、設計の正直さを優先するスタンスが滲む。
proc macroのコスト:proc macroのサポートがrust-analyzerの肥大化を招いた一因だとmatkladは振り返る。proc macroは実際にコードを実行するため通常のIDE最適化が効かず、生成コードも膨大になる。ある時点での計測では、rust-analyzerのバイナリサイズの30%がJSONパースコードで占められていたという。代替案として、Sorbet(Rubyの型チェッカー)が採用している手法——メタプログラミングを実行せず、その「効果」だけをシムで宣言する——を挙げている。serdeを展開する代わりにimpl Serialize for T {}という空の実装を注入するだけにする、というアプローチだ。
ファイル監視の問題:AIエージェントがコードを編集する際にインレイヒントがずれる問題については、rust-analyzerのネイティブファイルウォッチャーAPIに根本的な競合状態(race condition)があることを認め、プラットフォーム固有の修正を行わなかったと率直に述べている。設定のfiles.watcherオプションを切り替えると改善する可能性があるとのことだ。
Rust Glancerという外部プロジェクトへの賛辞を入口に、rust-analyzer自身の設計判断を掘り下げていく構成は読み応えがある。「100分の1のRAM」という数字が示す通り、問題はアーキテクチャの根幹にあった。実装の難しさより「やりきれなかった」という事実を正直に書いた、珍しい種類の技術ブログだ。
詳細はRust Glancerを参照していただきたい。