Rustでコマンドラインツールを開発する

コマンドラインツールをつくる際、今まではPythonやRubyなどのスクリプト言語をつかうことが多かった。
スクリプト言語はコンパイルする必要がなく、普通のシェルスクリプトよりかは書きやすいし、外部のライブラリも組込みやすい。
Java・ScalaなどのJVM系言語はC言語系よりかは書きやすいが、JVMの起動の時間が気になってしまいあまり向いていないように思われる。

しかしながら、スクリプト言語での開発にも問題はある。
動的に型がついて危ない、というのは小さくなりがちなちょっとしたツールでは比較的無視しやすいのでいいとする。
一番厄介なのは、実行環境のバージョン違いである。特にプロセス呼び出しで、同じ言語の他のツールを呼び出したりすると突然エラーを吐いたりすることがあった。

この前、社内でコマンドラインツールを書く機会があり、試しにRustで書いてみることにした。
一度コンパイルしてしまいバイナリにしてしまえば、バージョンの違いに苦しむことはないであろう。
変更のたびコンパイルする必要があるとはいえ、一度コンパイルさえしてしまえば高速に動作する。
実はRustの公式ワーキンググループの中にコマンドラインインターフェース(CLI)のためのチームがあり、ドキュメント・ライブラリが整備されている。

ドキュメントは優秀で、コマンドライン引数の処理やエラーハンドリングなどでどういうクレートを使えばいいかを紹介してくれている。

特にstructoptをつかったコマンドライン引数の処理は非常に簡単。シリアライズでお馴染みのserdeみたいに構造体にアトリビュートをつけていくと、コマンドライン引数を構造体に簡単に落とし込める。ヘルプメッセージなども簡単につくれるし、サブコマンドの定義なども柔軟に対応可能。

変更のたびコンパイルする手間であるが、cargo runコマンドを使うことである程度緩和できる。cargo runは変更がない限りは特にはコンパイル処理を行わないのでオーバーヘッドは小さい。
ただし、生で叩くのは面倒なので、以下のようなシェルスクリプトのラッパーを経由して呼び出すことにした。

1
2
3
4
script_path=$(readlink -f "$0")
manifest_path="$(dirname "$script_path")/Cargo.toml"

RUSTFLAGS=-Awarnings cargo run -q --release --manifest-path=$manifest_path -- "$@"

RUSTFLAGSはコンパイル時の警告を消すためにつけている。これをプロジェクトのルートディレクトリにおいておき、このスクリプトへのシンボリックリンクをパスの通っているところに配置すればコマンドラインから簡単に実行できる。
初回時のみ無言でコンパイルを行うが、その後はインクリメンタルにビルドしてくれるので、無言の期間は比較的短くてすむはずである。
もちろん、このようなスクリプトを使わずともバイナリのシンボリックリンクを置くとかでもよいが、そこはお好みで。