Rustで競技プログラミングをするときの"スニペット管理"をまじめに考える(cargo-snippetの紹介)

# はじめに

競技プログラミングでは基本的にmain関数を含む1ファイルを提出することになるので外部クレートを使用することができません。

なので必然的に標準ライブラリにない機能を使用したいとき等はスニペット(コード片)をどこかからコピペしてプログラムを書くことになります。

**ではそのスニペットたちはどのように管理すればよいでしょうか？**

そのことについて考えた結果[cargo-snippet](https://github.com/hatoo/cargo-snippet)というツールを開発するに至ったので紹介したいと思います。

Rustで(本格的なスニペット管理を必要とするほど)競技プログラミングをやっている方はあまりいないかもしれませんが参考になる方がいれば幸いです:bow:。

# 背景

![VimのプラグインUltisnipsによるスニペット操作の様子](https://qiita-image-store.s3.amazonaws.com/0/20535/6f1b3a90-b9a6-0b95-8ae2-71388f6c05f2.gif)

こんなかんじで、大抵のエディタにはあらかじめ登録しておいたスニペットを入力する機能があります。

しかし、エディタのスニペット機能だけでスニペットを管理しようとすると直接編集するのが面倒な上に**テストが書けない**という問題点があります。例えばVScodeでのスニペットの形式は[こんな感じ](https://vscode-doc-jp.github.io/docs/userguide/userdefinedsnippets.html)
ならばエディタのスニペット機能を使わないでクレート上にコード+テストを書いて、必要なときにコピペするというやり方も考えられますが、**面倒です**。

ということで、Rustのアノテーション機能を使うことで「**クレートからスニペットのみを抜き出す**」ことでこれらを解決するツールが[cargo-snippet](https://github.com/hatoo/cargo-snippet)です。

# インストール

rustfmtが必要なのでインストール
`$ rustup component add rustfmt`

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

# 使い方

まずスニペットのためのクレートを作成します。
このクレートにスニペットを書いていきます。

`$ cargo new mysnippet`

`Cargo.toml`の`dependencies`に`cargo-snippet`を追加します

```toml:Cargo.toml
[dependencies]
cargo-snippet = "0.4"
```

試しにスニペットとテストを書いてみます

```rust:src/lib.rs
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++に比べて）貧弱

で、これらはスニペットを使うことでほぼ解決できると思っています:muscle:。

自分の観測範囲では他の言語にはこのようなスニペット管理ツールはないのでRust競プロerは[cargo-snippet](https://github.com/hatoo/cargo-snippet)で他の言語に差をつけてはいかがでしょうか:grinning:

ちなみに私のスニペットは[hatoo/competitive-rust-snippets](https://github.com/hatoo/competitive-rust-snippets)で管理しています。