Help us understand the problem. What is going on with this article?

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

はじめに

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

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

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

そのことについて考えた結果cargo-snippetというツールを開発するに至ったので紹介したいと思います。

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

背景

VimのプラグインUltisnipsによるスニペット操作の様子

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

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

ということで、Rustのアノテーション機能を使うことで「クレートからスニペットのみを抜き出す」ことでこれらを解決するツールがcargo-snippetです。

インストール

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

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

使い方

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

$ cargo new mysnippet

Cargo.tomldependenciescargo-snippetを追加します

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

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

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で他の言語に差をつけてはいかがでしょうか:grinning:

ちなみに私のスニペットはhatoo/competitive-rust-snippetsで管理しています。

hatoo@github
Rustのお仕事ください!
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした