はい,巷にありふれたやつです.ただ,プロジェクト構成をどうするかなど,意外に非自明なところを書いている記事が少ないかもと思って書いてみます.昔Rustを始めたばかりの頃,A問題のためのプロジェクト,B問題のためのプロジェクト,(以下無限)を作ってた時代がありました.そういうので悩んでる人には刺さる可能性が少しあります.自環境を汚したくない人はdocker化した方がいいですがそこまでは話さないです.また,非RustユーザでもAHCのtesterがRustで提供されることが多いし,強い人にもRustが多いので,やっといて損ないかもしれないです.
- Rustのインストール
- 必要なCargoコマンドのインストール
- rust-analyzerのインストール
- プロジェクト作成の仕方
Rustのインストール
Rustの公式サイトの手順で一発です.WSL好きの皆さんはWindows TerminalからUbuntuを立ち上げて以下を打ちましょう.
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
これで,cargo
, rustc
, rustup
というコマンドがインストールされると思いますので,
cargo -V
rustc -V
rustup -V
でそれぞれ最新バージョンであることを確認しましょう.こいつら誰ですかという方はとがさんの記事等を参考にしてください.え,2023年9月現在はAtCoderのジャッジサーバはv1.70.0なのではと思った勘の良いガキの皆さん,最終章でお会いしましょう.
必要なCargoコマンドのインストール
上記でcargo
コマンドが使えるようになりました.cargo
はビルドツールですが,以下のサブコマンドも取り入れることでAtCoderライフが快適になります.
- cargo compete
- 色んな競プロプラットフォームで問題をローカルに落としてくる,テストする,提出するとかを実現するためのサブコマンドです
- AtCoder, yukicoderなどはもちろん,AtCoder Problemsに作ったバチャとかにも対応できます
- cargo member
- 子プロジェクトを親プロジェクトのメンバーとして登録する
- プロジェクト構成の仕方のところで活躍します
それぞれ,
cargo install cargo-compete
などとするとインストールできます.
他にもhatooさん謹製のcargo snippetは超便利ですが,Rust製自作ライブラリが増えてきたらどうぞです.
rust-analyzerのインストール
rust-analyzerはエディタで編集してる時に補完してくれたり文法を確認してくれたりする言語サーバープロトコル(Language Server Protocol, LSP)です.Linterみたいなものです.例えばRustでは型推論が働くので全ての変数に自明に型を指定する必要はないですが,rust-analyzerのお陰でどの変数が何型か教えてくれるようになったりならなかったり.VSCodeへのインストールは簡単で,下図のように拡張機能からrust-analyzerを探してインストールするのみです.
プロジェクト作成の仕方
ここまでで必要なアイテムはたぶん出揃いました.ではアルゴのためのRustプロジェクトを作りましょう.これが一番重要ですが,空の親プロジェクトを作り,その下にABC319, 320,...などのナンバリングごとの子プロジェクトを作っていくことになります(他にベストプラクティスあればコメントください!!!).以下のようになることを目指します.
algorithm_v1_70_0/
├── Cargo.toml
├── abc319
│ ├── Cargo.lock
│ ├── Cargo.toml
│ ├── src
│ └── testcases
├── abc320
│ ├── Cargo.lock
│ ├── Cargo.toml
│ ├── src
│ └── testcases
├── compete.toml
├── src
│ └── main.rs
└── template-cargo-lock.toml
親プロジェクト生成&Rustバージョン指定
WSLで任意のディレクトリに移動し,以下コマンドを実行して親プロジェクトを作ります.名前はなんでもいいです.自分はアルゴ用とヒューリスティック用を分けているので,例えば以下のような名前にしています.
cargo init algorithm_v1_70_0
これで普通に1つのRustプロジェクトが作られますが,こいつのsrc/main.rs
などは一生日の目を見ません.
そして,勘の良いガキの皆さんお久しぶりです.Rustはプロジェクトごとに使用するRustのバージョンを指定できます.ついでにこのプロジェクトでのバージョンを指定しましょう.以下のコマンドでv1.70.0のtoolchain
(cargoやrustcをひっくるめた名称と理解しています)を入れます.そして,親プロジェクト内でrust-toolchain
という名前のファイルにバージョンを書き込んでおきます.
cd algorithm_v1_70_0
rustup toolchain add 1.70.0-x86_64-unknown-linux-gnu
echo 1.70.0-x86_64-unknown-linux-gnu > rust-toolchain
そうするとあら不思議,cargo
, rustc
のバージョンが1.70.0になります.VSCodeでConnect to WSLし,この親プロジェクトを開いた状態で以下を進めてください.
cargo competeを使うための設定
親プロジェクトの中に移動し,以下コマンドを実行してcargo competeをAtCoderで使えるようにしましょう.
cargo compete init atcoder
以下の質問が来ると思いますが,自分は2で回答します.
Do you use crates on AtCoder?
1 No
2 Yes
3 Yes, but I submit base64-encoded programs
1..3: 2
2023年9月現在,デフォルトで作成されるcompete.toml
が,v1.42.0対応の古いものです.なので,以下に差し替えてください.使わないcrateはコメントアウトすればよいです.ビルド時間が命取りになる可能性もありますので...
# Path to the test file (Liquid template)
#
# Variables:
#
# - `manifest_dir`: Package directory
# - `contest`: Contest ID (e.g. "abc100")
# - `bin_name`: Name of a `bin` target (e.g. "abc100-a")
# - `bin_alias`: "Alias" for a `bin` target defined in `pacakge.metadata.cargo-compete` (e.g. "a")
# - `problem`: Alias for `bin_alias` (deprecated)
#
# Additional filters:
#
# - `kebabcase`: Convert to kebab case (by using the `heck` crate)
test-suite = "{{ manifest_dir }}/testcases/{{ bin_alias }}.yml"
# Open files with the command (`jq` command that outputs `string[] | string[][]`)
#
# VSCode:
#open = '[["code", "-a", .manifest_dir], ["code"] + (.paths | map([.src, .test_suite]) | flatten)]'
# Emacs:
#open = '["emacsclient", "-n"] + (.paths | map([.src, .test_suite]) | flatten)'
[template]
src = '''
fn main() {
todo!();
}
'''
[template.new]
# `edition` for `Cargo.toml`.
edition = "2021"
# `profile` for `Cargo.toml`.
#
# By setting this, you can run tests with `opt-level=3` while enabling `debug-assertions` and `overflow-checks`.
profile = '''
[dev]
opt-level = 3
'''
dependencies = '''
ac-library-rs = "=0.1.1"
once_cell = "=1.18.0"
static_assertions = "=1.1.0"
varisat = "=0.2.2"
memoise = "=0.3.2"
argio = "=0.2.0"
bitvec = "=1.0.1"
counter = "=0.5.7"
hashbag = "=0.1.11"
pathfinding = "=4.3.0"
recur-fn = "=2.2.0"
indexing = { version = "=0.4.1", features = ["experimental_pointer_ranges"] }
amplify = { version = "=3.14.2", features = ["c_raw", "rand", "stringly_conversions"] }
amplify_derive = "=2.11.3"
amplify_num = { version = "=0.4.1", features = ["std"] }
easy-ext = "=1.0.1"
multimap = "=0.9.0"
btreemultimap = "=0.1.1"
bstr = "=1.6.0"
az = "=1.2.1"
glidesort = "=0.1.2"
tap = "=1.0.1"
omniswap = "=0.1.0"
multiversion = "=0.7.2"
num = "=0.4.1"
num-bigint = "=0.4.3"
num-complex = "=0.4.3"
num-integer = "=0.1.45"
num-iter = "=0.1.43"
num-rational = "=0.4.1"
num-traits = "=0.2.15"
num-derive = "=0.4.0"
ndarray = "=0.15.6"
nalgebra = "=0.32.3"
alga = "=0.9.3"
libm = "=0.2.7"
rand = { version = "=0.8.5", features = ["small_rng", "min_const_gen"] }
getrandom = "=0.2.10"
rand_chacha = "=0.3.1"
rand_core = "=0.6.4"
rand_hc = "=0.3.2"
rand_pcg = "=0.3.1"
rand_distr = "=0.4.3"
petgraph = "=0.6.3"
indexmap = "=2.0.0"
regex = "=1.9.1"
lazy_static = "=1.4.0"
ordered-float = "=3.7.0"
ascii = "=1.1.0"
permutohedron = "=0.2.4"
superslice = "=1.0.0"
itertools = "=0.11.0"
itertools-num = "=0.1.3"
maplit = "=1.0.2"
either = "=1.8.1"
im-rc = "=15.1.0"
fixedbitset = "=0.4.2"
bitset-fixed = "=0.1.0"
proconio = { version = "=0.4.5", features = ["derive"] }
text_io = "=0.1.12"
rustc-hash = "=1.1.0"
smallvec = { version = "=1.11.0", features = ["const_generics", "const_new", "write", "union", "serde", "arbitrary"] }
'''
dev-dependencies = '''
#atcoder-202004-lock = { git = "https://github.com/qryxip/atcoder-202004-lock" }
'''
[template.new.copy-files]
"./template-cargo-lock.toml" = "Cargo.lock"
[new]
kind = "cargo-compete"
# Platform
#
# - atcoder
# - codeforces
# - yukicoder
platform = "atcoder"
# Path (Liquid template)
#
# Variables:
#
# - `contest`: Contest ID. **May be nil**
# - `package_name`: Package name
path = "./{{ contest }}"
#[new]
#kind = "oj-api"
#url = "https://atcoder.jp/contests/{{ id }}"
#path = "./{{ contest }}"
# for Library-Checker
#[add]
#url = "https://judge.yosupo.jp/problem/{{ args[0] }}"
##is-contest = ["false"] # optional
##target-kind = "bin" # ["bin", "example"]. default to "bin"
#bin-name = '{{ args[0] }}'
##bin-alias = '{{ args[0] }}' # optional
##bin-src-path = './src/bin/{{ bin_alias }}.rs' # optional
# for yukicoder
#[add]
#url = '{% case args[0] %}{% when "contest" %}https://yukicoder.me/contests/{{ args[1] }}{% when "problem" %}https://yukicoder.me/problems/no/{{ args[1] }}{% endcase %}'
#is-contest = ["bash", "-c", '[[ $(cut -d / -f 4) == "contests" ]]'] # optional
##target-kind = "bin" # ["bin", "example"]. default to "bin"
#bin-name = '{% assign segments = url | split: "/" %}{{ segments[5] }}'
##bin-alias = '{% assign segments = url | split: "/" %}{{ segments[5] }}' # optional
##bin-src-path = './src/bin/{{ bin_alias }}.rs' # optional
[test]
# Toolchain for the test. (optional)
#toolchain = "1.42.0"
#toolchain = "nightly"
# Profile for `cargo build`. ("dev" | "release")
#
# Defaults to `"dev"`.
#profile = "dev"
#[submit.transpile]
#kind = "command"
#args = ["cargo", "equip", "--exclude-atcoder-crates", "--resolve-cfgs", "--remove", "docs", "--minify", "libs", "--rustfmt", "--check", "--bin", "{{ bin_name }}"]
##language_id = ""
[submit]
kind = "file"
path = "{{ src_path }}"
language_id = "5054"
子プロジェクトとしてのナンバリングごとのプロジェクト作成
ゴールが近いです.親プロジェクト内で以下を打ってみてください.
cargo compete new abc320
cargo member include abc320
そうすると,以下のような感じになるかと思います.
.
├── Cargo.toml
├── abc320
│ ├── Cargo.lock
│ ├── Cargo.toml
│ ├── src
│ │ └── bin
│ └── testcases
│ ├── a
│ ├── a.yml
│ ├── b
│ ├── b.yml
│ ├── c
│ ├── c.yml
│ ├── d
│ ├── d.yml
│ ├── e
│ ├── e.yml
│ ├── f
│ ├── f.yml
│ ├── g
│ └── g.yml
├── compete.toml
├── src
│ └── main.rs
└── template-cargo-lock.toml`
また,親プロジェクトのCargo.toml
に以下のように書き込まれるはずです.
workspace = {members = ["abc320"],exclude = []}
これによって,abc320がalgorithm_v1_70_0の子プロジェクトであることが認識されます.たぶん.
VSCodeを親プロジェクトで開いた状態で,全ての子プロジェクトに対してrust-analyzer
が働く状態になってるかと思います.
A問題を実際に解いてみてください.型チェックや補間は効いてますでしょうか.提出前にテストしたくなったら以下を打ってください.
cd abc320
cargo compete t a
ローカルに落として来たテストケースをよしなに実行してくれます.標準エラーも出してくれるのでこれでデバッグが事足りることも多いです.ちなみに以下のコマンドだと,普通のRustバイナリの実行になります.
cargo run --bin abc320-a
いざ提出!
cargo compete s a
or
cargo compete s a --no-test (ローカルテストなし)
ローカルテストなしは,AHCもしくはPCスペックが低くて想定解でもTLEしちゃうときに使います.ジャッジ結果がリアルタイムに返ってきて,無事ACになったでしょうか.
さいごに
もしかしたら非自明かもしれないRustでAtCoder環境を作るときのプロジェクト構成に焦点を置いて書いてみました.WSL最高!VSCode最高!cargo compete最高!です.質問・加筆修正案等あればコメントお願いします.