はじめに
競技プログラミングでは基本的にmain関数を含む1ファイルを提出することになるので外部クレートを使用することができません。
なので必然的に標準ライブラリにない機能を使用したいとき等はスニペット(コード片)をどこかからコピペしてプログラムを書くことになります。
ではそのスニペットたちはどのように管理すればよいでしょうか?
そのことについて考えた結果cargo-snippetというツールを開発するに至ったので紹介したいと思います。
Rustで(本格的なスニペット管理を必要とするほど)競技プログラミングをやっている方はあまりいないかもしれませんが参考になる方がいれば幸いです。
背景
こんなかんじで、大抵のエディタにはあらかじめ登録しておいたスニペットを入力する機能があります。
しかし、エディタのスニペット機能だけでスニペットを管理しようとすると直接編集するのが面倒な上にテストが書けないという問題点があります。例えばVScodeでのスニペットの形式はこんな感じ
ならばエディタのスニペット機能を使わないでクレート上にコード+テストを書いて、必要なときにコピペするというやり方も考えられますが、面倒です。
ということで、Rustのアノテーション機能を使うことで「クレートからスニペットのみを抜き出す」ことでこれらを解決するツールがcargo-snippetです。
インストール
rustfmtが必要なのでインストール
$ rustup component add rustfmt
$ cargo install cargo-snippet --features="binaries"
使い方
まずスニペットのためのクレートを作成します。
このクレートにスニペットを書いていきます。
$ cargo new mysnippet
Cargo.toml
のdependencies
にcargo-snippet
を追加します
[dependencies]
cargo-snippet = "0.4"
試しにスニペットとテストを書いてみます
use cargo_snippet::snippet;
// こんなかんじでアノテーションで以下の関数がスニペットであることを指定します
// この場合mymathとgcdという名前のスニペットであることを表しています
#[snippet("mymath")]
#[snippet("gcd")]
fn gcd(a: u64, b: u64) -> u64 {
if b == 0 {
a
} else {
gcd(b, a % b)
}
}
// こっちの書き方でもいいです
#[snippet(name = "mymath")]
// 名前を省略すると関数名がそのままスニペット名になります
#[snippet]
fn lcm(a: u64, b: u64) -> u64 {
a / gcd(a, b) * b
}
#[snippet]
// スニペットに依存関係がある場合は以下のように指定できます
#[snippet(include = "gcd")]
fn gcd_list(list: &[u64]) -> u64 {
list.iter().fold(list[0], |a, b| gcd(a, b));
}
#[test]
fn test_gcd() {
assert_eq!(gcd(57, 3), 3);
}
#[test]
fn test_lcm() {
assert_eq!(lcm(3, 19), 57);
}
もちろんテストできます!
$ cargo test
最後に、cargo snippetコマンドでスニペットを抽出できます!
デフォルトではVimのプラグインであるNeosnippetの形式で出力します。
$ cargo snippet
snippet gcd
fn gcd(a: u64, b: u64) -> u64 {
if b == 0 {
a
} else {
gcd(b, a % b)
}
}
snippet gcd_list
fn gcd(a: u64, b: u64) -> u64 {
if b == 0 {
a
} else {
gcd(b, a % b)
}
}
fn gcd_list(list: &[u64]) -> u64 {
list.iter().fold(list[0], |a, b| gcd(a, b));
}
snippet lcm
fn lcm(a: u64, b: u64) -> u64 {
a / gcd(a, b) * b
}
snippet mymath
fn gcd(a: u64, b: u64) -> u64 {
if b == 0 {
a
} else {
gcd(b, a % b)
}
}
fn lcm(a: u64, b: u64) -> u64 {
a / gcd(a, b) * b
}
現在、以下の形式の出力に対応しています
- Neosnippet
- VScode
- Ultisnips
-t
オプションで出力形式を指定できます。
やり方はcargo snippet -h
で確認してみてください。
おわりに
個人的に、Rustで競技プログラミングをやるときの大きな欠点は
- 入出力等のボイラープレートが多い
- 標準ライブラリが(C++に比べて)貧弱
で、これらはスニペットを使うことでほぼ解決できると思っています。
自分の観測範囲では他の言語にはこのようなスニペット管理ツールはないのでRust競プロerはcargo-snippetで他の言語に差をつけてはいかがでしょうか
ちなみに私のスニペットはhatoo/competitive-rust-snippetsで管理しています。