Rust ではコンパイル時にクレートの特定の機能の有効/無効を切り替えることのできる フィーチャーフラグ (feature flag) と呼ばれる機能があり, うまく使えばとても便利です. この記事でその使い方をまとめます.
クレート利用者向け
自分が依存したいクレートがフィーチャーフラグを持つとき, Cargo.toml
で指定することによってそれを有効化します. 例えば rand
クレートを serde1
フラグを立てた状態で使用したい場合は
[dependencies]
rand = { version = "0.7", features=["serde1"] }
となります. クレートによってはデフォルトでいくつかのフラグが有効になっていて, しかしそれを無効化したいということがあります. その場合は default-features=false
を指定します.
クレート作成者向け
以下がこの記事の主題です. 自クレートでフィーチャーフラグを立てる方法を説明します.
フィーチャーフラグの定義
あるクレートで独自にフィーチャーフラグを設定する場合, Cargo.toml
に features
セクションを設けてその中で定義します. 例えば:
[features]
default = []
japanese = []
default
でデフォルトで有効なフィーチャーフラグをまとめて設定します (なくても構いません). ともかく, これでフィーチャーフラグ japanese
が使えるようになったので, Rust コードの中で cfg
アトリビュートを用いてフラグの有無で関数の定義などを分岐します.
#[cfg(not(feature="japanese"))]
pub fn hello() -> &'static str {
"hello"
}
#[cfg(feature="japanese")]
pub fn hello() -> &'static str {
"こんにちは"
}
#[cfg(test)]
mod tests {
use crate::*;
#[test]
fn it_works() {
assert_eq!( hello(), "hello" );
}
}
見ての通り, 関数 hello
は, フラグ japanese
が立っていないときには "hello"
を, 立っているときには "こんにちは"
を返します. なおこの例では関数定義に cfg
アトリビュートを付与しましたが, cfg!
マクロを使えば同等の実装をより簡潔に書くことができます.
pub fn hello() -> &'static str {
if cfg!(feature="japanese") {
"こんにちは"
} else {
"hello"
}
}
#[cfg(test)]
// 以下省略 //
しかし, フラグが立っているときにのみある関数を定義したいという状況ではアトリビュートを使うことになります.
さて, 上のコードに含まれるテストはフラグ japanese
が立っていないときには成功し, 立っていれば失敗するでしょう. 普通に cargo test
を実行すると, デフォルトではフラグを立てていないため, テストをパスします. が, --features
オプションを通じてフラグを立ててコンパイルすれば失敗するようにできます.
$ cargo test
Compiling a v0.1.0 (/home/osanshouo/misc/featureflag/a)
Finished test [unoptimized + debuginfo] target(s) in 0.68s
Running target/debug/deps/a-2627f60e1e33f0a2
running 1 test
test tests::it_works ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
$
$
$ cargo test --features japanese
Compiling a v0.1.0 (/home/osanshouo/misc/featureflag/a)
Finished test [unoptimized + debuginfo] target(s) in 0.71s
Running target/debug/deps/a-48b8540e44e9729b
running 1 test
test tests::it_works ... FAILED
failures:
---- tests::it_works stdout ----
thread 'tests::it_works' panicked at 'assertion failed: `(left == right)`
left: `"こんにちは"`,
right: `"hello"`', src/lib.rs:26:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
failures:
tests::it_works
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out
error: test failed, to rerun pass '--lib'
外部クレートの制御
外部クレート (例えば rand
) に optional で依存しているとき, 自動的にそのクレート名 (rand
) というフィーチャーフラグが設定されます. そして, そのフラグが立っているときにのみその外部クレートをビルドします. 例えば次の例ではデフォルトでは rand
はビルドされませんが, japanese
フラグが立っているときには rand
フラグも立ち, ビルドされます.
[features]
default = []
japanese = [ "rand" ]
[dependencies]
rand = { version = "0.7", optional = true }
この振る舞いのために, フィーチャーフラグの名称としては自分が依存しているクレートの名前を使うことはできません. なお自分がいま開発しているクレートからは, 依存クレートが持つフィーチャーフラグは (クレート名)/(フィーチャー名)
という形で見えています. なのでフィーチャー名がブッキングしてもそれで意図せず依存クレートのフラグが立つということはありません.
この性質を利用して, 依存クレートのフィーチャーフラグを自クレートのフィーチャーフラグを通じて制御することができます. 例えば rand
クレートの serde1
フラグを自クレートの japanese
フラグが立っているときにのみ有効化するには
[features]
default = []
japanese = [ "rand/serde1", ]
[dependencies]
rand = "0.7"
とします.