[Rust] フィーチャーフラグの使い方

Rust ではコンパイル時にクレートの特定の機能の有効/無効を切り替えることのできる __フィーチャーフラグ__ (feature flag) と呼ばれる機能があり, うまく使えばとても便利です. この記事でその使い方をまとめます.

# クレート利用者向け

自分が依存したいクレートがフィーチャーフラグを持つとき, `Cargo.toml` で指定することによってそれを有効化します. 例えば `rand` クレートを `serde1` フラグを立てた状態で使用したい場合は

```toml:Cargo.toml
[dependencies]
rand = { version = "0.7", features=["serde1"] }
```

となります. クレートによってはデフォルトでいくつかのフラグが有効になっていて, しかしそれを無効化したいということがあります. その場合は `default-features=false` を指定します.

# クレート作成者向け

以下がこの記事の主題です. 自クレートでフィーチャーフラグを立てる方法を説明します.

## フィーチャーフラグの定義

あるクレートで独自にフィーチャーフラグを設定する場合, `Cargo.toml` に `features` セクションを設けてその中で定義します. 例えば:

```toml:Cargo.toml
[features]
default = []
japanese = []
```

`default` でデフォルトで有効なフィーチャーフラグをまとめて設定します (なくても構いません). ともかく, これでフィーチャーフラグ `japanese` が使えるようになったので, Rust コードの中で `cfg` アトリビュートを用いてフラグの有無で関数の定義などを分岐します.

```rust:src/lib.rs
#[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!` マクロを使えば同等の実装をより簡潔に書くことができます.

```rust:src/lib.rs
pub fn hello() -> &'static str {
    if cfg!(feature="japanese") {
        "こんにちは"
    } else {
        "hello"
    }
}

#[cfg(test)]
// 以下省略 //
```

しかし, フラグが立っているときにのみある関数を定義したいという状況ではアトリビュートを使うことになります.

さて, 上のコードに含まれるテストはフラグ `japanese` が立っていないときには成功し, 立っていれば失敗するでしょう. 普通に `cargo test` を実行すると, デフォルトではフラグを立てていないため, テストをパスします. が, `--features` オプションを通じてフラグを立ててコンパイルすれば失敗するようにできます.

```shell
$ 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` フラグも立ち, ビルドされます.

```toml:Cargo.toml
[features]
default = []
japanese = [ "rand" ]

[dependencies]
rand = { version = "0.7", optional = true }
```

この振る舞いのために, フィーチャーフラグの名称としては自分が依存しているクレートの名前を使うことはできません. なお自分がいま開発しているクレートからは, 依存クレートが持つフィーチャーフラグは `(クレート名)/(フィーチャー名)` という形で見えています. なのでフィーチャー名がブッキングしてもそれで意図せず依存クレートのフラグが立つということはありません. 

この性質を利用して, 依存クレートのフィーチャーフラグを自クレートのフィーチャーフラグを通じて制御することができます. 例えば `rand` クレートの `serde1` フラグを自クレートの `japanese` フラグが立っているときにのみ有効化するには

```toml:Cargo.toml
[features]
default = []
japanese = [ "rand/serde1", ]

[dependencies]
rand = "0.7"
```

とします.


# 参考文献

* [The [features] section - The Manifest Format - The Cargo Book](https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-section)
* [Conditional compilation - The Rust Reference](https://doc.rust-lang.org/reference/conditional-compilation.html)