AtCoder用にVSCodeを用いたRustの開発環境の構築をした話

最近RustでAtCoderを始めました。始めるにあたってVSCodeでのRustの開発環境の構築をしたので、そのメモを書いていきます。

注意: この記事は2021年9月に書かれたもので、とても古いです! (AtCoder での Rust のバージョンが 1.42.0 だった時代に書かれた記事です)

環境

  • macOS Big Sur 11.5.2
  • 2021年9月21日時点での情報です。

Rust/VSCodeの開発環境の構築

まずは、AtCoder以外のプロジェクトにも必要な部分の開発環境の構築をします。

rustupのインストール

Rust をインストール - Rustプログラミング言語 に従ってrustupをダウンロードし、Rustの諸ツールをインストールします。

$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

途中、次のように聞かれるので1を選択します。

1) Proceed with installation (default)
2) Customize installation
3) Cancel installation
>

これで、rustupと以下のRustのツールがインストールされます。

次のように言われるので、ターミナルを再起動します。

To get started you may need to restart your current shell.
This would reload your PATH environment variable to include
Cargo's bin directory ($HOME/.cargo/bin).

インストールできているかを確認します。

$ rustc -V
rustc 1.55.0 (c8dfcfe04 2021-09-06)

$ cargo -V
cargo 1.55.0 (32da73ab1 2021-08-23)

$ rustup -V
rustup 1.24.3 (ce5817a94 2021-05-31)
info: This is the version for the rustup toolchain manager, not the rustc compiler.
info: The currently active `rustc` version is `rustc 1.55.0 (c8dfcfe04 2021-09-06)`l

rust-analyzer(VSCode拡張機能)のインストール

VSCodeでRustを使いやすくするために、rust-analyzer(VSCode拡張機能)をインストールします。

rust-analyzer(VSCode拡張機能)には、主に以下の機能があります(リンク先より引用)。

  • code completion, imports insertion
  • go to definition, implementation, type definition
  • find all references, workspace symbol search, rename
  • types and documentation on hover
  • inlay hints
  • semantic syntax highlighting

似た拡張機能として、Rust(VSCode拡張機能)があるのですが、rust-analyzer(VSCode拡張機能)のほうが使いやすかったので、rust-analyzer(VSCode拡張機能)を使用しています。

例えば、次のようなコードを書く場合には、Rust(VSCode拡張機能)ではmapsumなどの補完が効かなかったのですが、rust-analyzer(VSCode拡張機能)ではこれらの補完が効きます。

(0..3).map(|x| 2*x).sum::<i32>();

VSCodeによるコンパイル・実行

VSCodeによるコンパイル・実行をしてみたいと思います。cargo newを用いて試しに適当なプロジェクトを作成し、VSCodeで開きます。

$ cargo new test_project
$ code test_project

VSCodesrc/main.rsを開きます。そこにはすでにHello Worldのコードが用意されています。

main関数の上の部分にRun|Debugがあり、このボタンを押すとリリースモードでの実行やソースコードでの実行ができます。 右下に次のようなエラーが出た場合は、CodeLLDB(VSCode拡張機能)をインストールします。

RunDebugを押して実行すると、ターミナルのタブにHello, world!と出力されます。

なお、VSCodeではなくコマンドで実行する場合は、以下のように実行できます。

$ cargo run
   Compiling test_project v0.1.0 (/Users/test/rust_test/test_project)
    Finished dev [unoptimized + debuginfo] target(s) in 0.46s
     Running `target/debug/test_project`
Hello, world!

Clippy(Linter)の設定

次にRustのLinterであるClippyの設定をします。ClippyははじめのRustのインストールですでにインストールされています。 rust-analyzer(VSCode拡張機能)でClippyが使えるように設定をします。

VSCodeの設定を開き、拡張機能のRust Analyzerを選びます。Check On Save: Commandという項目があり、デフォルトはcheckになっているので、これをclippyに変更します。

setting.jsonで設定する場合には、setting.jsonに次を追加します。

    "rust-analyzer.checkOnSave.command": "clippy"

次にClippyの動作確認をします。 試しに先程のmain.rsに次のようなコードを書いてみます。

fn main() {
    let _x = 3.14159;
    println!("Hello World");
}

そうすると、3.14159に赤い波線が表示され、マウスオーバーすると次のような警告が表示されます。

このように良くないコードに対して警告や注意を出してくれるのがClippyの機能です。

参考: How to use Clippy in VS Code with rust-analyzer? - help - The Rust Programming Language Forum

AtCoder特有の開発環境の構築

これまでに、AtCoderに関係ない部分の開発環境の構築の話をしてきました。ここからはAtCoder特有の話をしていきます。

AtCoderで使われているRustのバージョン・クレート

まずは、AtCoderで使われているRustのバージョン、AtCoderで使えるクレートの確認をします。

AtCoderの適当なコンテストのルール(例えば、ルール - AtCoder Beginner Contest 220)を確認するとRustのバージョンが1.42.0であることが確認できます。

また、このページの下の方に以下のような記述があります。

このページのリンク先に飛ぶと、使用可能なライブラリ(Rustではクレートと呼ぶ)が確認できます。

これらの使用可能なクレートについては 2020 Update · rust-lang-ja/atcoder-rust-resources Wiki · GitHub のページで詳しく解説されています(このページはものすごい便利です)。

Rustのバージョンの固定化

2022年1月1日追記: 2021年10月の中旬から、rust-analyzerがRust1.42.0に対応しなくなりました。以下のようにRustのバージョンを1.42.0にするとrust-analyzerが機能しなくなります。1.48.0のようなrust-analyzerが使用可能なバージョンに設定するか、もしくはバージョンの固定化をしないことをおすすめします。

AtCoderで使われているRustのバージョンは1.42.0です。一方、自分の環境のRustが1.42.0とは限りません。

$ rustc -V
rustc 1.55.0 (c8dfcfe04 2021-09-06)

$ cargo -V
cargo 1.55.0 (32da73ab1 2021-08-23)

バージョンが違うと、自分の環境では大丈夫だが、AtCoderの環境ではコンパイルエラーになるということがあります。(例えばbool::thenという便利な関数があるのですが、これは1.50.0からの機能なので、AtCoderでは使用できません。)

そこで、特定のプロジェクトを使う場合にのみRustのバージョンが1.42.0になるように設定します。

バージョンを1.42.0にしたいプロジェクトにrust-toolchainというファイルを作成し、次の1行をrust-toolchainに書きます。

1.42.0

その後、Rustの適当なコマンドを実行すると、1.42.0のRustがインストールされます。

$ cargo -V
info: syncing channel updates for '1.42.0-x86_64-apple-darwin'
info: latest update on 2020-03-12, rust version 1.42.0 (b8cedc004 2020-03-09)
info: downloading component 'cargo'
info: downloading component 'clippy'
info: downloading component 'rust-docs'
info: downloading component 'rust-std'cd 
info: downloading component 'rustc'
info: downloading component 'rustfmt'
info: installing component 'cargo'
info: installing component 'clippy'
info: installing component 'rust-docs'
info: installing component 'rust-std'
info: installing component 'rustc'
info: installing component 'rustfmt'
cargo 1.42.0 (86334295e 2020-01-31)

バージョンを確認してみると、確かに1.42.0に変更されています。

$ cargo -V
cargo 1.42.0 (86334295e 2020-01-31)

AtCoderで使えるクレートをCargo.tomlに指定

AtCoderで使えるクレートを自分の環境でも使えるようにします。

AtCoder用のCargo.tomlの雛形がatcoder-rust-base/Cargo.toml at ja · rust-lang-ja/atcoder-rust-base · GitHubに記載されています。

AtCoderで使用可能なクレートについては上記の雛形の[dependencies]以下に記述されています。

このCargo.tomlの雛形(特に[dependencies]以下)をcargo initで生成されたCargo.tomlに貼り付け、使いたいクレートのコメントアウトを外すことで、クレートを使用することができます。

参考: Cargoパッケージの作成 (TODO) - AtCoderコンテストにRustで参加するためのガイドブック (cargo-generateは使用していませんが、もしかしたら便利かもしれません)

これで、AtCoderをRustで解くのに必要な環境は構築できました。以降、あると便利な設定やツールなどを紹介します。

複数のエントリーポイントの設定

cargo new で作成したプロジェクトの場合、エントリーポイントはsrc/main.rsmain関数のみです。AtCoderでは問題ごとにエントリーポイント(main関数)を作りたくなります(1つの問題に1つのプロジェクトという運用もあるかもですが)。 ここではCargo.tomlを編集してエントリーポイントを増やします。

例えば、ABC219のAとB用のソースコードをそれぞれsrc/abc219/maina.rs, src/abc219/mainb.rsに用意したとします。このままでは、Run|Debugのボタンがなく、実行ができません。

実行できるようにするために、Cargo.tomlに以下の記述を追加します。

[[bin]]
name = "abc219_a"
path = "src/abc219/maina.rs"
    
[[bin]]
name = "abc219_b"
path = "src/abc219/mainb.rs"
    

すると、Run|Debugのボタンが現れて、実行できるようになります。

VSCodeではなくコマンドで実行する場合は、Cargo.tomlnameの値を指定すると実行できます。

$ cargo run --bin abc219_a
    Finished dev [unoptimized + debuginfo] target(s) in 0.00s
     Running `target/debug/abc219_a`
Hello, world! - ABC219-A

スニペット管理ツール: cargo-snippet

競技プログラマー向けのRustのスニペット管理ツールとして、cargo-snippetがあります。以下のようにインストールできます。(Rustのバージョンが1.42.0の環境でインストールしようとすると失敗します。その場合はrust-toolchainのあるプロジェクトの外で実行すれば大丈夫です。)

# rustfmtがインストールされていない場合はrustfmtをインストール
# 普通はRustのインストール時に一緒にインストールされている
$ rustup component add rustfmt

$ cargo install cargo-snippet --features="binaries"

cargo-snippetの使い方については、cargo-snippetの作者による以下の解説記事に詳しく書いてあります。

qiita.com

ここでもcargo-snippetの使い方について軽く紹介します。

まずは、インストールしたcargo-snippetのバージョンを確認します。

$ cargo snippet -V
cargo-snippet 0.6.5

次にCargo.toml[dependencies]以下に次の記述を追加します。バージョンは上記で確認したバージョンが良いかと思います。

cargo-snippet = "0.6.5"

次にスニペット用のコードを書きます。例えばリンク先でも記述されているgcdmain.rsに書いてみます。

use cargo_snippet::snippet;

#[snippet]
fn gcd(a: u64, b: u64) -> u64 {
    if b == 0 {
        a
    } else {
        gcd(b, a % b)
    }
}

スニペットが書けたら、cargo snippet -t vscodeというコマンドを実行します(ここではVSCodeでの開発環境構築をしているのでVSCode用のスニペットを出力させていますが、Vim用のスニペットを出力させることもできます。)

$ cargo snippet -t vscode
{
  "gcd": {
    "prefix": "gcd",
    "body": [
      "fn gcd(a: u64, b: u64) -> u64 {",
      "    if b == 0 {",
      "        a",
      "    } else {",
      "        gcd(b, a % b)",
      "    }",
      "}"
    ]
  }
}

出力されたコードを.vscode/cargo_snippet.code-snippetsに保存します。(拡張子が合っていればファイル名は何でもいいです)

すると、gcdというスニペットが使えるようになり、選択すると先程書いた関数gcdのコードが展開されます。

ちなみに、自分のスニペットatcoder_rust/src/mylib at main · paruma/atcoder_rust · GitHub に置いてあります。(ModIntやUnionFindなどを置いてます。)

個人的なスニペットの置き場

自分はスニペットのコードをsrc/mylibというフォルダの中に置いています(参考: atcoder_rust/src/mylib at main · paruma/atcoder_rust · GitHub )。 ただし、そのまま置いてもrust-analyzer(VSCode拡張機能)がこのプロジェクトのソースコードと認識してくれず、あまり警告を表示してくれなかったり、型ヒントが表示されなかったりします。そこで、src/mylibに置いたソースコードをこのプロジェクトのソースコードであると認識させる必要があります。

まずはsrc/mylibというフォルダを認識させます。まずはsrc/lib.rsというファイルを作成し、次のように記述します。

pub mod mylib;

次にsrc/mylibの中のファイルを認識させます。例えばsrc/mylib/gcd.rsを認識させる場合、src/mylib.rsを作成し、次のように記述します。

pub mod gcd;

すると、src/mylib/gcd.rsが認識され、ちゃんと警告などが表示されるようになります。

この設定により、テストコードの実行もできるようになります。

個人的なcargo-snippetの使い方

cargo snippet -t vscodeで出力されるスニペットについて少し言及しておきます。

{
  "gcd": {
    "prefix": "gcd",
    "body": [
      "fn gcd(a: u64, baa: u64) -> u64 {",
      "    if b == 0 {",
      "        a",
      "    } else {",
      "        gcd(b, a % b)",
      "    }",
      "}"
    ]
  }
}

このスニペットはRustファイル以外のファイル(例えばCargo.toml)を書いているときであっても、サジェストの候補として表示されてしまいます。

これを防ぐためには、出力されたスニペットにRustファイル用のスニペットであることを追加で指定する必要があります。具体的には、次のように"scope": "rust",を追加する必要があります。

{
  "gcd": {
    "scope": "rust", // 追加した行
    "prefix": "gcd",
    "body": [
      "fn gcd(a: u64, baa: u64) -> u64 {",
      "    if b == 0 {",
      "        a",
      "    } else {",
      "        gcd(b, a % b)",
      "    }",
      "}"
    ]
  }
}

これは、sedを使いcargo snippetの出力を置換して出力させれば解決できます。

$ cargo snippet -t vscode | sed -r "s/\"prefix\"/\"scope\": \"rust\",\n    \"prefix\"/"
{
  "gcd": {
    "scope": "rust",
    "prefix": "gcd",
    "body": [
      "pub fn gcd(a: u64, b: u64) -> u64 {",
      "    if b == 0 {",
      "        a",
      "    } else {",
      "        gcd(b, a % b)",
      "    }",
      "}"
    ]
  }
}

また、スニペットを更新した際に、いちいち.vscode/cargo_snippet.code-snippetsに出力されたスニペットを追加するのは面倒です。そこで、次のようなシェルスクリプトsnippet.shを作成し、このシェルスクリプトを実行することで自動で.vscode/cargo_snippet.code-snippetsが更新されるようにしています。

#!/bin/sh

cargo snippet -t vscode | sed -r "s/\"prefix\"/\"scope\": \"rust\",\n    \"prefix\"/" > .vscode/cargo_snippet.code-snippets

個人的なプロジェクトのディレクトリ構造

AtCoder用のプロジェクトを作成する際には、以下の3パターンが考えられます。

  • 1つの問題に対して1つのプロジェクトを作る。
  • 1つのコンテストに対して1つのプロジェクトを作る。
  • 1つのAtCoder全体のプロジェクトを作り、すべてのコンテストでこのプロジェクトを使用する。

ここらへんは好みの問題だと思いますが、自分は3つ目を採用しています。(他のコンテストのソースコードを素早く参照したいのと、複数プロジェクトあると管理が大変そうなため。)

AtCoder用のプロジェクトのディレクトリ構造は以下のようにしています。(いくつか省略しています)

.
├── .vscode: VSCodeプロジェクトの設定フォルダ
|   ├── cargo_snippet.code-snippets: cargo-snippetが出力するスニペットの置き場
|   └── rust.code-snippets: その他の手動で書いたスニペットの置き場
├── src
│   ├── contest: コンテスト用のディレクトリ
│   │   ├── abc207
│   │   │   ├── mainb.rs
│   │   │   └── mainc.rs
│   │   ├── abc208
│   │   │   ├── mainb.rs
│   │   │   ├── mainc.rs
│   │   │   └── maind.rs
│   │   └── template.rs: ソースコードのテンプレート
│   ├── mylib: スニペットのソースコードの置き場
│   │   ├── bin_search0.rs
│   │   ├── modint_field.rs
│   │   └── union_find0.rs
│   ├── sandbox: なにか適当なソースコードを書きたくなったときに書く場所
│   │   └── sandbox1.rs
│   ├── lib.rs
│   ├── mylib.rs
│   └── sandbox.rs
├── Cargo.lock
├── Cargo.toml
├── rust-toolchain
├── snippet.sh: 上記で解説したスニペット生成用スクリプト
├── web_task.sh: 問題ページを開くシェルスクリプト(後述)
└── contest.py: コンテスト用のコードなどを用意するスクリプト(後述)

このプロジェクトは以下のGitHubに置いてあります。詳しくはそちらを参照してください。

https://github.com/paruma/atcoder_rustgithub.com

その他用意したツール

コンテスト名と問題を指定したら問題のページが開かれるツール

次のようなシェルスクリプトweb_task.shを作成しました。

#!/bin/sh

if [ $# -ne 2 ]; then
    echo "usage: ./web_task.sh [contest_name] [task_name]" 1>&2
    exit 1
fi
open "https://atcoder.jp/contests/$1/tasks/$1_$2"

例えば、./web_task.sh abc219 bを実行すると、https://atcoder.jp/contests/abc219/tasks/abc219_b が開かれます。 (Alfred(Macのランチャーアプリ)で同様の機能を実装すると便利そうだなぁって思っているが作ってはいない。)

ソースファイルの生成ツール

新しい問題を解くときにいちいちファイルを作成してテンプレートをコピーしてCargo.tomlに設定追加してってやるのは面倒なので、それらのことをやってくれるスクリプトcontest.pyソースコード)を作りました。

例えば./contest.py abc206 b cを実行すると、src/contest/abc206/mainb.rssrc/contest/abc206/mainc.rsが生成されます。ファイルの中身はあらかじめ用意したテンプレートファイルsrc/contest/template.rsをコピーしたものです。さらに、Cargo.toml用の記述が以下のように出力されます。

$ ./contest.py abc206 b c
[[bin]]
name = "abc206_b"
path = "src/contest/abc206/mainb.rs"
    
[[bin]]
name = "abc206_c"
path = "src/contest/abc206/mainc.rs"
    

これをCargo.tomlに貼り付けてソースコードが実行できるようにしています。

使ってはいないが便利そうなツール

コンテスト用のソースファイルの生成からテストケースでのテスト、コードの提出までコマンドでできるツールとして、cargo-atcoderやcargo-competeがあります。自分は使用していませんが、人によっては便利だと思うので軽く紹介しておきます。

qiita.com

blog.foresta.me

最後に

自分が行った、AtCoder用のVSCodeでのRustの開発環境の構築について紹介しました。

ぜひ皆さんもRustでAtCoderをやってみましょう!

リンク集