Rustのビルドシステムであるcargoは、PATHにcargo-なんとか
というバイナリが存在するとサブコマンドとして使用できます。よくあるやつですね。便利なものがcrates.io上に公開されているので、いくつかご紹介したいと思います。
cargo install
その前に、cargo組み込みのコマンドであるcargo install
の説明です。Rust製バイナリの導入が簡単に行えるコマンドです。cargoパッケージ1のディレクトリ内で
cargo install
と実行すると、そのパッケージをコンパイルし、成果物のバイナリを~/.cargo/bin
にインストールします。cargo uninstall バイナリ名
でアンインストールできます。
crates.io上のパッケージをコンパイル&インストールするにはパッケージ名を指定します。
cargo install ripgrep
Gitリポジトリから取得することも可能です。
cargo install --git https://github.com/BurntSushi/ripgrep.git
注意点として、cargo install
コマンドでは既に同名のバイナリが存在する場合-f
オプションを付けない限り上書きされません。
この記事では項目毎にcrates.io上のパッケージ名を括弧の中に書いているので、気になるものがあればcargo install パッケージ名
でインストールしてみてください。
cargo install-update
(cargo-update
)
さて、cargo install
は便利なのですが、インストールしたものを一括でアップデートしてくれるような機能はありません。個々のパッケージが更新される度に手動で
cargo install -f なんとか
と打たなければなりません。cargoはパッケージマネージャではなくビルドシステムなので、あくまでmake install
的なコマンドということのようです(更新機能を追加するissueは立てられていますが)。
しかしパッケージの更新を自分で調べてアップデートするのは不便なので、自動化するツールが存在します。cargo-update
です。パッケージ名と異なり、cargo-install-update
というバイナリがインストールされます。
使い方は簡単です。
cargo install-update -a
これだけでcrates.ioからインストールしたパッケージが更新されます。gitリポジトリからインストールしたものの自動更新は今の所できないようです。
cargo outdated
(cargo-outdated
)
Cargoのバージョン指定はSemVerなのですが、デフォルト(比較演算子を省略した場合)がnpmでいう^
になっています。
[dependencies]
clap = "1.0.0" # >=1.0.0 <2.0.0
toml = "0.1.0" # >=0.1.0 <0.2.0
cargo update
を行うと、この制約に合致する範囲でcrates.io上の最新バージョンが選択されます。しかし、crates.io上にこの範囲を越える新しいバージョンが公開されていたとしてもcargoは教えてくれません。なので気が付いたら古いバージョンのライブラリを使い続けていたなんてことがあります。
そこで役立つのがcargo-outdated
です。cargo update
を実行すると、現在のプロジェクトが依存しているパッケージの新しいバージョンが公開されている場合に教えてくれます。
> cat Cargo.toml
[package]
name = "hello"
version = "0.1.0"
[dependencies]
clap = "1.0.0"
toml = "0.1.0"
> cargo update
Updating registry `https://github.com/rust-lang/crates.io-index`
> cargo outdated
Checking for SemVer compatible updates...Done
Checking for the latest updates...Done
The following dependencies have newer versions available:
Name Project Ver SemVer Compat Latest Ver
clap 1.5.5 -- 2.19.3
clap->ansi_term 0.7.5 -- 0.9.0
clap->bitflags 0.3.3 -- 0.7.0
clap->strsim 0.4.1 -- 0.6.0
clap->vec_map 0.4.0 -- 0.6.0
toml 0.1.30 -- 0.2.1
Project Ver
が現在使用されているバージョンで、SemVer Compat
はcargo update
を実行することで更新できるバージョン、Latest Ver
はCargo.tomlを書き換えないと更新されないバージョンを示しています。
cargo tree
(cargo-tree
)
Rust界隈では小さなクレートをたくさん作る傾向があるようです。なので、ちょっとしたプロジェクトのつもりでも間接的に多数のクレートに依存していたりします。この依存クレートをツリー形式で表示してくれるのがcargo-tree
です。
racerパッケージでcargo tree
を実行してみた結果を示します。
> cargo tree
racer v2.0.3
├── clap v2.19.1
│ ├── ansi_term v0.9.0
│ ├── bitflags v0.7.0
│ ├── libc v0.2.18
│ ├── strsim v0.5.2
│ ├── term_size v0.2.1
│ │ └── libc v0.2.18 (*)
│ ├── unicode-segmentation v0.1.3
│ ├── unicode-width v0.1.3
│ └── vec_map v0.6.0
├── env_logger v0.3.5
│ ├── log v0.3.6
│ └── regex v0.1.80
│ ├── aho-corasick v0.5.3
│ │ └── memchr v0.1.11
│ │ └── libc v0.2.18 (*)
│ ├── memchr v0.1.11 (*)
│ ├── regex-syntax v0.3.9
│ ├── thread_local v0.2.7
│ │ └── thread-id v2.0.0
│ │ ├── kernel32-sys v0.2.2
│ │ │ └── winapi v0.2.8
│ │ └── libc v0.2.18 (*)
│ └── utf8-ranges v0.1.3
├── log v0.3.6 (*)
├── syntex_errors v0.52.0
│ ├── libc v0.2.18 (*)
│ ├── log v0.3.6 (*)
│ ├── rustc-serialize v0.3.22
│ ├── syntex_pos v0.52.0
│ │ └── rustc-serialize v0.3.22 (*)
│ ├── term v0.4.4
│ │ ├── kernel32-sys v0.2.2 (*)
│ │ └── winapi v0.2.8 (*)
│ └── unicode-xid v0.0.3
├── syntex_syntax v0.52.0
│ ├── bitflags v0.7.0 (*)
│ ├── libc v0.2.18 (*)
│ ├── log v0.3.6 (*)
│ ├── rustc-serialize v0.3.22 (*)
│ ├── syntex_errors v0.52.0 (*)
│ ├── syntex_pos v0.52.0 (*)
│ ├── term v0.4.4 (*)
│ └── unicode-xid v0.0.3 (*)
├── toml v0.2.1
│ └── rustc-serialize v0.3.22 (*)
└── typed-arena v1.2.0
各クレートは最大一回だけ依存ツリーが展開され、二回目の出現からは(*)
という印が付き省略されます。
cargo graph
(cargo-graph
)
cargo-tree
はターミナル上にツリーを描画するものでしたが、こちらは依存クレートのグラフを描画するコマンドです。cargo graph
を実行するとdot言語でグラフが出力されるので、煮るなり焼くなりできます。
また例はracerです。
cargo graph | dot -Gsplines=ortho -Earrowhead=open -Earrowsize=0.5 -Tpng -Ograph.png
cargo modules
(cargo-modules
)
コードのモジュール構造をツリー形式で表示するものです。各モジュールの可視性(pub
か否か)や条件コンパイル属性を表示してくれます。
ripgrepのrg
バイナリを構成するモジュールを見てみましょう。
> cargo modules -b rg
rg : crate
├── app : private
├── args : private
├── atty : private
├── pathutil : private
├── printer : private
│ └── tests : private @ #[cfg(test)]
├── search_buffer : private
│ └── tests : private @ #[cfg(test)]
├── search_stream : private
│ └── tests : private @ #[cfg(test)]
├── unescape : private
│ └── tests : private @ #[cfg(test)]
└── worker : private
cargo clippy
(clippy
)
lintツールです。必須です。各lintの説明が並んでいるhttps://github.com/Manishearth/rust-clippy/wiki をざっと眺めるだけでもかなり役に立ちます。
Nightlyコンパイラが必要なので、clippyの為にローカルではNightlyで開発・CIでstableで動くかチェックみたいな感じにしている人も多そうですが、そろそろ標準添付されそうな雰囲気ですね。
以前はちょっと試してみるだけでもCargo.tomlを弄ったり#![plugin]
属性を入れたりと面倒な作業が必要だったような気がしますが、最近では一度実行するだけならパッケージのディレクトリ内で
cargo clippy
と打つだけになったようです。注意点として、clippyをコンパイルするのに使ったのと同じコンパイラでないと動作しません。rustup update
でNightlyコンパイラを更新したらcargo install -f clippy
としてclippyを再コンパイルする必要があります。
参考記事: rust-clippyの使い方
cargo benchcmp
(cargo-benchcmp
)
Rustには言語組み込みのベンチマーク機能があります(unstable)。cargo-benchcmp
はそのベンチマークを簡単に比較できるツールです。
benches/moji.rs
というベンチマークコードがあるとします。次のようにして、コード変更前と変更後でベンチマーク結果を書き出します。
> cargo bench --bench moji > old
> ...
> cargo bench --bench moji > new
これらをbenchcmpコマンドに引数として渡すことで比較が行われます。実行時間の変化がパーセンテージで表示されます。もし新しい方のベンチマークで性能が低下した場合、赤くハイライトされます(この例では13%の性能低下が起きているので、ターミナル上の文字は赤くなっていました)。
> cargo benchcmp old new
name old ns/iter new ns/iter diff ns/iter diff %
bench_moji_count 38 43 5 13.16%
cargo expand
(cargo-expand
)
Rustはマクロやトレイトのderive
といったコード生成機構を備えています。マクロ呼び出しや#[derive]
属性はコンパイル時に実際のソースコードへ展開されるのですが、生成されたコードに問題がある場合、コンパイラのエラーメッセージからは問題の箇所がよく分からない場合があります。
cargo expand
はコンパイラによって展開された後のコードを表示してくれるツールです。実際のコードを確認することで生成されたコードの問題点が見つけやすくなります。
cargo expand
の出力例を示します。マクロやderiveだけでなく、stdのインポートなどの定型コードも追加されていることが分かりますね。
元コード:
#[derive(Debug, Clone)]
struct Foo<T>(T);
fn bar() {
println!("{}", 3.14);
}
cargo expand
の出力:
#![feature(prelude_import)]
#![no_std]
#[prelude_import]
use std::prelude::v1::*;
#[macro_use]
extern crate std as std;
struct Foo<T>(T);
#[automatically_derived]
#[allow(unused_qualifications)]
impl<T: ::std::fmt::Debug> ::std::fmt::Debug for Foo<T> {
fn fmt(&self, __arg_0: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
match *self {
Foo(ref __self_0_0) => {
let mut builder = __arg_0.debug_tuple("Foo");
let _ = builder.field(&&(*__self_0_0));
builder.finish()
}
}
}
}
#[automatically_derived]
#[allow(unused_qualifications)]
impl<T: ::std::clone::Clone> ::std::clone::Clone for Foo<T> {
#[inline]
fn clone(&self) -> Foo<T> {
match *self {
Foo(ref __self_0_0) => Foo(::std::clone::Clone::clone(&(*__self_0_0))),
}
}
}
fn bar() {
::io::_print(::std::fmt::Arguments::new_v1(
{
static __STATIC_FMTSTR: &'static [&'static str] = &["", "\n"];
__STATIC_FMTSTR
},
&match (&3.14,) {
(__arg0,) => [::std::fmt::ArgumentV1::new(__arg0, ::std::fmt::Display::fmt)],
},
));
}
おわりに
他にもまだこういった便利なツールが存在します。ここなどを参考に探してみて下さい。
-
1つのパッケージには1つ以上のクレートが含まれます。あまり厳格に区別する必要はなく、パッケージのことをクレートと呼んだりします。 ↩