Yabutan 技術ブログ > Rust 書き捨てコードに適したプロジェクト構成

Rust 書き捨てコードに適したプロジェクト構成

#Rust

2022-11-28

この記事では、私がRustで書き捨てコードを書く際にやっている
Rustプロジェクトの構成について紹介します。

Rustでは、cargo new すれば簡単にプロジェクト作成することはできます。

だた、毎回ちょっとした処理を書くために、Cargo.tomlから設定を書いていくのも面倒でしょう。
しかも、私はRustを書く時にCLionを使っているので、毎回プロジェクトディレクトリに登録するのも億劫になります。

書き捨てコードとは

私が言っている書き捨てコードとは、
ちょっとした処理を実装する際に使うスクリプト的なものです。

例えば、MySQLなどのデーターベースから取得したデータを、何かしらの形に加工して、 CSVなどに出力したいとか。 複数のJsonファイルを読み込んで、特定条件で突き合わせて抽出したりなど。

何かしらの入力ファイルがあって、処理を行った後、成形して出力したい。
みたいなことが大半です。
あとは、ちょっとした処理を実験したいときにも書き捨てコードを作ります。

書き捨てコードとは、 一時的な利用を目的としたコードです。

examples ディレクトリを使う

Rustで書き捨てコードを書くときに、
毎回、cargo new でプロジェクトディレクトリを作成するよりも、
私がオススメするのは、examplesディレクトリを活用して管理していくことです。

examplesというディレクトリには、rsファイルを配置する事ができて、
それぞれ、exampleごとに個別に実行する事ができます。

examplesディレクトリを使った構成例
project_tree

上図の赤線をひいたファイルは、全てexampleとして個別に実行する事が可能になります。
cargo run —example <Example名> や、
cargo test —example <Example名>などで実行とテストを個別で動かすことができます。

私は基本、YYYYMMDD_xxxxx みたいに日付のディレクトを作って、
そこに main.rsファイルを配置するというスタイルでやっています。

他にも、src/bin 配下にサブディレクトリを作るという方法や、benches, tests ディレクトリを使うという手もありますが、 examplesディレクトリを使うのが、このようなケースでは一番相性が良いと思います。

src/bin配下は、ターゲットビルド時に、サブディレクトリ毎にバイナリを生成しようとするので、
ファイル数が増えてくるとコンパイル時間にとても時間がかかってしまいます。

benchesと、tests ディレクトリは、ちょっと用途が違いすぎるのと、 CLionなどのIDE上で実行すると、コンソール出力がテスト用に収集されてしまうというのもあって、不便だったりします。 (これは設定で変えれますが)

example run で実行する

examplesディレクトリ配下のコードは、 cargo run, cargo test で個別に実行する事ができます。
実行する際には、cargo run —example xxxxx と、example名を指定する必要があります。

一度、cargo run —example とだけ実行すると、指定できるexample名を出力してくれます。
cargo_run

テストコードの方は、
cargo test —example <Example名> と同様に個別に実行指定できますが、
cargo test —examples で全てのexamples を指定してテスト実行する事もできます。

ちなみに単純に、cargo test とした場合、 examples配下のテスト項目は実行されませんが、
コンパイル対象にはなっているので、コードエラーがあれば検出します。

data, out ディレクトリ

ディレクトリ名はなんでもいいんですが、
私は書き捨てコード用の、入力ファイルと出力ファイル専用のディレクトリを、
各exampleディレクトリ配下につくるようにしています。

またこれらは、gitにコミットする必要ないファイルという事がおおいので、
.gitignoreで無視するようにしています。

.gitignore

/examples/*/data
/examples/*/out

このような設定をしておくと、
下図の赤枠内のファイルは、git管理ファイルとしては無視してくれます。
gitignore

カレントディレクトリの設定

実行する際のカレントディレクトリは、各exampleディレクトリ直下であることの方が、都合がよいので、 書き捨てコードの冒頭では、下記のような set_directory関数をコールしておくと便利です。

// カレントディレクトリをソースファイルの親ディレクトリに設定
set_current_dir(Path::new(file!()).parent().unwrap()).unwrap();

上記だと、プロジェクトルート以外の場所から実行したときに、エラーになるので CARGO_MANIFEST_DIR環境変数を利用した方法も記載しておきます。

// カレントディレクトリをソースファイルの親ディレクトリに設定
// (プロジェクトルート以外の場所から実行しても、対応できるようにCARGO_MANIFEST_DIR環境変数を利用)
set_current_dir(
    PathBuf::from_iter([env!("CARGO_MANIFEST_DIR"), file!()]).parent().unwrap(),
).unwrap();

file!マクロは、プロジェクトルートを基準とした相対パスを返すため、 プロジェクトルートのPathを取得できる CARGO_MANIFEST_DIR環境変数を利用しています。

まとめ

  • examples配下には、子ディレクトリ/main.rs の形で、いくつも作成することができる。
  • examples配下のコードは、cargo <run|test> —example <example> でそれぞれ、個別に実行が可能。
  • examples配下であればtargetビルドの負荷にならない。(src/binと比べて)
  • 書き捨てだけど、日付ディレクトリで管理できるので、再利用がしやすい。
  • 1元管理できるので、前も使ったなという部分は、lib配下にまとめ易くなる。