Rust IDE に化ける VSCode

初めまして.社内の好きなエディタアンケートVisual Studio Code (VSCode) に 1 票を投じた R&D チームの久保です.普段は深層学習を用いた画像解析やその周辺の研究開発を行っています.

アンケートでは惜しくも Vim 勢に敗れてしまいましたが,2015 年にリリースされてからというもの,ものすごいスピードでユーザを増やしているこの VSCode を使って Rust の開発を行いたい人向けの記事を書いてみました.この記事を読むまで Rust (rls)CodeLLDB の拡張機能を知らなかったという人がいれば今すぐインストールしましょう.私がこの 11101 文字を使って言いたいのはそれだけです.

「Rust って何?」という方はぜひ以下の過去記事にも目を通してみてください.

必修言語Rustの他己紹介

Rust カテゴリーの記事一覧 - OPTiM TECH BLOG

はじめに

どのプログラミング言語にしても,書く機会が増えてくると必然的に「いかに開発環境を充実させていくか」という課題が出てくると思います.幸いなことに Rust は過去記事である必修言語Rustの他己紹介でも少し触れられているように,

など様々な周辺ツールが公式に用意されており,簡単に使うことができます.

これらの便利ツールの力があれば Rust を開発する準備はあっという間に整うことでしょう.今回はその中でも,rls と VSCode を連携させることからはじめ,VSCode を Rust IDE 化する方法を紹介していきます.

この記事の内容は macOS,Linux,Windows のいずれにも共通する内容ではありますが,執筆時には macOS 10.14 でしか動作確認を行っていないためご注意ください.

rls との連携

Language Server とは

先ほども出てきた rls というのはいわゆる Language Server の一つで,Language Server というのは Language Server Protocol で定められたプロトコルに従って,フロントエンド (IDE やエディタ) に対して IDE として必要な機能を提供するための情報を送信するバックエンドサーバのことを指します.Language Server Protocol はもともと Microsoft が VSCode 向けに開発していた仕様を標準化しオープンにしたものなので,最近では色々な言語の Language Server が出てきたり,フロントエンドとして使えるエディタ等もだいぶ増えてきました.

Language Server はフロントエンド (IDE やエディタ) とは並列で動作するので,エディタ等の動作をブロックすることなくコードを解析したりコンパイルしたりして得られた情報をフロントエンドに提供してくれます.フロントエンドはその情報を使って実際にユーザに対してコード補完を表示したり,エラーや警告の箇所を教えてくれたりするという訳です.

Rust (rls) 拡張機能でできること

VSCode には rls のフロントエンドとなる拡張機能 Rust (rls) が Rust 公式で出ています.

rustup で Rust ツールチェーンや rls が既にインストールされた環境であれば,VSCode でこの拡張機能をインストールするだけで以下のような機能が使えるようになります.

  1. コードの補完

    f:id:optim-tech:20190716211911p:plain

  2. 関数や変数等の定義へのジャンプ (or インライン表示),関数や変数等を参照している場所の検索,シンボルの検索

    f:id:optim-tech:20190716210707g:plain

  3. 型推論結果やドキュメントのホバー表示

    f:id:optim-tech:20190716205941g:plain

  4. コーディングスタイルに従った自動フォーマット

    f:id:optim-tech:20190716203809g:plain

  5. リファクタリング

    コードを解釈した上で,関数や変数などのリネーム等を一発で行ってくれます.

    f:id:optim-tech:20190716203506p:plain:w422

  6. コンパイラやリンタのエラー,警告の表示や解決策の提案

    f:id:optim-tech:20190716203305p:plain

  7. コードスニペット

    f:id:optim-tech:20190716212734g:plain

  8. ユニットテストを個別に実行

    テストケースを自動認識してインラインで "Run test" ボタンが表示され,個別に実行することができます.

    f:id:optim-tech:20190716195749p:plain

rls の将来

少し脱線しますが,rls はコードのコンパイルに標準のツールチェーンをそのまま使っています.そのため通常の cargo build などでのビルド結果と同等の結果が得られるという恩恵がありますが,一方でコード量が大きくなってきたり依存が増えてくるとコンパイルが遅くて補完などが効き始めるまでに時間がかかりイライラするということもあります.IDE で有名な JetBrains の IntelliJ Rust という Rust IDE を実現するプラグイン (IntelliJ IDEA,CLion,PyCharm 向け) では,独自の Rust コンパイラを搭載することで高いパフォーマンスを実現していたりしますが,コンパイラが完全ではないなどいくつかの問題を抱えています.(この辺りは以下の関連記事が詳しく解説してくださっています)

当然それらを問題視して解決しようと動く人はいる訳ですが,rls は根本の設計をやり直そうにも既にかなり使われていて難しいということで,rls 2.0 というアーキテクチャを一から設計し直すワーキンググループが発足しているようです.その最初の実装として Rust Analyzer というものも既にあり,今後の発展に期待ですね.

デバッグ機能の活用

CodeLLDB 拡張機能の使い方

VSCode といえば様々な言語に対応することができるデバッグ機能を標準搭載していることはご存知かと思いますが,前述の rls 連携だけではそれを活用できません.

VSCode デバッグ機能を使って Rust プログラムをデバッグするには拡張機能 CodeLLDB をインストールすることで可能です.CodeLLDB は名前の通り,ビルドしたプログラムを LLDB を使ってデバッグしてくれます.最近はこの拡張機能自体も部分的に Rust で書かれ始めているようです.

VSCode ではデバッグ実行時の挙動を設定する必要があり,ディレクトリ単位 (VSCode でいうところの Workspace 単位) もしくはグローバルに設定しておけますが,今回はディレクトリ単位での設定を行います.

まず,デバッグ実行したい Rust プロジェクトのルートディレクトリを VSCode で開きます.そしてサイドバーからデバッグタブ (下記画像の赤丸) をクリックしてデバッグ用のサイドパネルを開きます.

f:id:optim-tech:20190716190325p:plain:h300

そして設定ボタン (下記画像の赤丸) をクリックします.

f:id:optim-tech:20190716190306p:plain:h450

カレントディレクトリに .vscode/launch.json が存在する場合はそれが開かれるだけです,存在しない場合は新規に launch.json が作成されて開かれます.新規に作成される場合はテンプレートを選択させられるので,LLDB (下記画像の赤丸) を選択します.

f:id:optim-tech:20190716190244p:plain

作成される際,VSCode で開いているディレクトリに Cargo.toml が存在すれば

{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "type": "lldb",
            "request": "launch",
            "name": "Debug executable '<プロジェクト名>'",
            "cargo": {
                "args": [
                    "build",
                    "--bin=<プロジェクト名>",
                    "--package=<プロジェクト名>"
                ],
                "filter": {
                    "name": "<プロジェクト名>",
                    "kind": "bin"
                }
            },
            "args": [],
            "cwd": "${workspaceFolder}"
        },
        {
            "type": "lldb",
            "request": "launch",
            "name": "Debug unit tests in executable '<プロジェクト名>'",
            "cargo": {
                "args": [
                    "test",
                    "--no-run",
                    "--bin=<プロジェクト名>",
                    "--package=<プロジェクト名>"
                ],
                "filter": {
                    "name": "<プロジェクト名>",
                    "kind": "bin"
                }
            },
            "args": [],
            "cwd": "${workspaceFolder}"
        }
    ]
}

のような launch.json が自動生成されます.(以前は自前で書く必要があったので,こんなにも楽になっていたことを執筆時に知って感動しています)

以降は Configuration のプルダウンメニュー

f:id:optim-tech:20190716195729g:plain

から "Debug executable ..." を選択して実行すれば,ビルドした生成物でのデバッグ実行,"Debug unit tests ..." を選択して実行すればユニットテストのデバッグ実行が行えます.

デバッグ実行では,あらかじめ以下のようにソースコードの行番号の左側をクリックして

f:id:optim-tech:20190716200329g:plain

breakpoint を設定しておくことで,実行時にその行で実行を一時中断させ,変数の内容やどの分岐に入っているのかをチェックしたりステップ実行したりすることができたり,実行時エラーの際には発生箇所を含めたコールスタックを確認したりするといった基本的なデバッグ機能が使用できます.

f:id:optim-tech:20190716185004p:plain

また,LLDB を使用しているため,以下の参考記事にあるように,Rust のコードと C++ のコードが混在していてリンクされるようなプロジェクトの場合,Rust コードと C++ コードのどちらともデバッグが可能です.

launch.json のカスタマイズ

実際の Rust プロジェクトでは feature を複数持ち,機能のオン・オフを切り替えていたり,実行時にコマンドライン引数に何らかの値を与える必要があったりすると思います.そういった時は先程のデバッグ用のサイドパネルにある設定ボタンを押すかもしくはプロジェクトのルートディレクトリ以下に生成されているであろう .vscode/launch.json を直接開き,既存の configuration を編集するか,もしくは既存の configuration を元に少し改変した configuration に別名をつけて追加することになります.

例えば,cargo build 時に feature の指定が必要な場合などは

{
    ...中略
    "configurations": [
        ...中略
        {
            "type": "lldb",
            "request": "launch",
            "name": "Debug executable '<プロジェクト名>' with feature_a and feature_b",
            "cargo": {
                "args": [
                    "build",
                    "--bin=<プロジェクト名>",
                    "--package=<プロジェクト名>",
                    "--features",
                    "feature_a feature_b"
                ],
                ...中略
            },
            ...中略
        },
        ...中略
    ],
    ...中略
}

のように対象の configuration の中の cargoargs に追加することで指定できます.上の例では feature_a という feature と feature_b という feature を有効にしてビルドするように指定しています.このように cargo build の引数として指定するようなものはここに指定します.よく使うものだと,リリースビルドを行う "--release" もここに指定します.

一方で,実行時のコマンドライン引数であったり,環境変数であったりといったものは

{
    ...中略
    "configurations": [
        ...中略
        {
            "type": "lldb",
            "request": "launch",
            "name": "Debug executable '<プロジェクト名>' with arguments",
            ...中略
            "args": [
                "引数1",
                "引数2"
            ],
            "env": {
                "RUST_LOG": "<プロジェクト名>=trace"
            },
            ...中略
        },
        ...中略
    ],
    ...中略
}

のように対象の configuration の中の args (実行時のコマンドライン引数) や env (環境変数) に追加します.先程 feature を指定する時に使用した cargoargs と今回の args は別ものであり,前者が cargo に対する引数で,後者がビルドされた実行ファイルに対する引数であるという点に注意してください.

デバッグ実行前のビルドはバックグラウンドで行われ,その結果はデバッグコンソールの Output タブで確認できます.ビルドエラー時などはこれが自動で開かれます.

f:id:optim-tech:20190716190223p:plain

これを常に確認したい場合はデバッグ実行時にデバッグコンソールを開くような設定を追加しておくと楽でしょう.以下のように設定できます.

{
    ...中略
    "configurations": [
        ...中略
        {
            "type": "lldb",
            "request": "launch",
            "name": "Debug executable '<プロジェクト名>'",
            ...中略
            "internalConsoleOptions": "openOnSessionStart"
        },
        ...中略
    ],
    ...中略
}

LLDB コマンド直接入力

もしもデバッグ実行中に,リンクされた共有ライブラリを確認したり,メモリの内容をそのままファイルに書き出したりといった発展的なことがしたくなったとしても統合ターミナルを立ち上げて LLDB を実行したりする必要はありません.

デバッグコンソールを開くと標準出力や標準エラーで出力された内容の下にプロンプトがあると思います.JavaScript のデバッグ時などはこれがブラウザの開発者ツールのコンソールのような役割を果たす訳ですが,CodeLLDB だとここに LLDB のコマンドを直接入力することができます.

f:id:optim-tech:20190716200654g:plain

LLDB のコマンドについては以下のドキュメント等を参照してください.

リモートマシン上のコードをデバッグ

少し前に Visual Studio および VSCode 向けに Remote Development が発表されて話題になっていましたね.私も NVIDIA 製 GPU を必要とする CUDA プログラム等を実行する場合は手元のマシンで動かせないため,この機能を重宝しています.嬉しいことに Remote Development でも CodeLLDB による Rust プロジェクトのデバッグが行えます.もちろん実行はリモートマシン上でしてくれます.

手順は簡単で,"Remote - SSH""Remote - Containers""Remote - WSL" のいずれかの拡張機能を使ってリモートマシン上のディレクトリを開き,ローカルマシン上でセットアップした時と同様に CodeLLDB のインストールと .vscode/launch.json の作成を行うだけです.たったこれだけで,リモートマシン上で実行させつつまるでローカルマシン上で実行しているかのようにシームレスにデバッグ実行が行えます.

Remote Development が登場する以前は,SSHFS 等でリモートマシンのファイルをマウントしてローカルマシンの VSCode で開く方法であったり,リモートマシン上で VSCode を実行して X11 Forwarding & VirtualGL で表示する方法であったり,Live Share 機能を自分同士で使う方法であったりで部分的には可能でしたが,どれもこの用途においては使い勝手の悪いものでした.そういったこともあって,Remote Development の登場は開発フローを大きく変えたといっても過言ではありません.

最後に

今回は主に VSCode の拡張機能 Rust (rls)CodeLLDB の使い方及び何ができるのかについて解説しました.Rust の周辺ツールの充実具合の一端を,そして VSCode の強力さを感じていただけたのではないでしょうか.

普段使う開発環境をいかに充実させていくかというのもソフトウェアエンジニアの大事な業務の一環です.常により便利なツールを手に入れるべくアンテナを張っているようなアグレッシブな仲間を私たちは探しています.

夏季インターンシップも締め切り間近です!