【Rust】Cargoターゲットの使い分け方

にあえん

August 3, 2023

やっと…仕事でRustを触れるようになりました!!!

嬉しい!!

とはいえ、Rustでたまに実装しているものの、基本pythonでしか実装していない日々だったので、どういうルールがあったのかを忘れてしまいました。。

まずはCargoターゲットについて学習していきたいと思い、その備忘録になります。

CargoパッケージとCargoターゲットとは

Rustでプロジェクトを作るときはまずcargo new [プロジェクト名]のように、cargoを利用してプロジェクトの基盤をまず作ると思います。

ここで作られたプロジェクトのことは「Cargoパッケージ」と言います。

Cargoパッケージには以下に分類されるコードを含めることができます。

Cargo Targets - The Cargo Book

library

ライブラリは、Cargoパッケージから参照される想定のコードのことです。

CLIのようなバイナリとして実行するアプリケーションではなく、他のRustライブラリが参照することを想定して実装する場合はこちらを使います。

binary

文字通りバイナリとしてビルドし実行する想定のコードのことです。

こちらはライブラリのように他のRustライブラリが参照するというよりは、単体で動作するソフトウェアを作るのに役立ちます。

example

主にライブラリの使用例を表すコードのことです。

cargoの各コマンドで使用例を使うためのサポートがされています。

You can run individual executable examples with the cargo run command with the –example option.

Library examples can be built with cargo build with the –example option.

cargo install with the –example option can be used to copy executable binaries to a common location.

和訳: cargo runコマンドで–example オプションを指定すると、実行可能なサンプルを個別に実行できます。

cargo buildコマンドで–example オプションを指定すると、ライブラリのサンプルをビルドできます。

cargo installコマンドで–example オプションを指定すると、実行可能なバイナリを共通の場所にコピーできます。

Cargo Targets - The Cargo Book

コードサンプルは積極的に書かねば…

test

テストコードのことです。

Rustでは#[test]というマクロを使用することでテスト関数を定義することが可能です。

pub fn add(left: usize, right: usize) -> usize {
    left + right
}

#[test]
fn test_add() {
    assert_eq!(add(2, 2), 4);
}

この関数は一つの関数に対する単純なテストを行います。

これを実行するには、Cargoパッケージでcargo testと入力するだけです。

適当に作ったパッケージでテストを実行してみます。

% cargo test
   Compiling helper v0.1.0 (/home/username/Project/study/rust-lib-and-bin-playground/helper)
    Finished test [unoptimized + debuginfo] target(s) in 0.50s
     Running unittests src/lib.rs (target/debug/deps/helper-a63b3aa7dc61fba1)

running 1 test
test test_add ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

   Doc-tests helper

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

個別にテストを実装する場合はこのように同一ファイルに実装することが多いようですが、インテグレーションテストのような他の関数と組み合わせる必要のあるテストはtestsという別のディレクトリをCargoパッケージ内に作成します。

参考: dieselのインテグレーションテスト実装

benchmark

パフォーマンステストを行うコードを表します。

nightlyでのみ使用可能。

試しに先程の関数に#[bench]をつけた状態で試してみます。

#![feature(test)]

extern crate test;

pub fn add(left: usize, right: usize) -> usize {
    left + right
}

#[cfg(test)]
mod tests {
    use super::*;
    use test::Bencher;

    #[bench]
    fn bench_add_two(b: &mut Bencher) {
        b.iter(|| add(2, 2));
    }
}

#[test]と違って、#[bench]は下準備としてtestsモジュールの定義が必要だったり、nightlyとして使う必要があったりするので、個人的に使うツールじゃないなら安定リリース版で使えるベンチマークツールを使ったほうがいいかも?

criterion

補足: Cargoプロジェクトで共通のRustバージョンを使用したい場合

nightlyを使用する場合、Cargoパッケージがnightlyを使用することを明言しておきたいですよね。

#[bench]マクロをローカルで試すにあたって、そのへんが気になったので調べたら、「rust-toolchain」というファイルを作るのがいいそう。

プロジェクトで使用するRustツールチェインのバージョンをチームで共有する - Qiita

プロジェクトで開発する際はこのへん必須になりそうですね。

2023-08-05 追記

#[bench]のコードサンプルで、#[cfg(test)]というマクロを使っていますが、なくても実装可能です。

#![feature(test)]

extern crate test;

use test::Bencher;

pub fn add(left: usize, right: usize) -> usize {
    left + right
}

#[bench]
fn bench_add_two(b: &mut Bencher) {
    b.iter(|| add(2, 2));
}

#[cfg(test)]は、cargo testおよびcargo benchを行った際にのみコンパイルされるため、ビルドした際に容量を節約できると言ったメリットがあります。

testsモジュールの#[cfg(test)]という注釈は、コンパイラにcargo buildを走らせた時ではなく、cargo testを走らせた時にだけ、 テストコードをコンパイルし走らせるよう指示します。これにより、ライブラリをビルドしたいだけの時にはコンパイルタイムを節約し、 テストが含まれないので、コンパイル後の成果物のサイズも節約します。

テストの体系化 - The Rust Programming Language 日本語版

なので、積極的に#[cfg(test)]は使うべきですね。っていう補足でした。