July 08, 2020

English | 日本語

Rust の Crate で Features を使う (日本語)

Rust のライブラリを作っているとしよう。

"greetings" の feature を有効にしたときだけ関数 "hello()" を公開したい場合はどうすればよいだろうか?

普通に "hello()" を公開する crate

まずはローカルの crate と、その関数を使う実行ファイルを作ってみよう。

ライブラリ crate "foo"

Shell で以下のように実行。

# shell

$ cargo new --lib foo

次に foo/src/lib.rs を編集。

// foo/src/lib.rs

pub fn hello() {
    println!("Hello world.");
}

"foo::hello()" を呼ぶ実行ファイル bar

Shell で以下のように実行

# shell

$ cargo new --bin bar

次に bar/src/main.rs を編集。

// bar/src/main.rs

extern crate foo;

fn main() {
   foo::hello();
}

最後に bar/Cargo.toml の [dependencies] セクションに以下の行を追加。

# bar/Cargo.toml

...

[dependencies]
foo = { version = "^0", path = "../foo" }

...

Shell で実行すると "Hello world." と表示される。

# shell

$ (cd bar; cargo run)

"hello()" を "greetings" feature にする

"foo::hello()" が正常動作することを確認したら、これを feature にしてみる。

まず foo に feature を作る。

foo/Cargo.toml に "[features]" セクションをつくり "greetings" を追加。

# foo/Cargo.toml

...

[features]
greetings = []

...

(空の角カッコは、その feature が他の crate に依存していない事を示す。詳細は後述。)

次に "hello()" を "greetings" 特有の機能にする。 '[cfg(feature = "greetings")]' を foo/src/lib.rs に追加。

// foo/src/lib.rs

#[cfg(feature = "greetings")]
pub fn hello() {
    println!("Hello world.");
}

この段階で bar のコンパイルが通らなくなるので bar/Cargo.toml の依存関係を修正する。

// bar/Cargo.toml

...

[dependencies]
foo = { version = "^0", path = "../foo", features = ["greetings"] }

...

長い行が気に入らなければ "[dependencies.foo]" セクションを作っても良い。

# bar/Cargo.toml

...

[dependencies.foo]
version = "^0"
path = "../foo"
features = ["greetings"]

...

bar のビルドが通るはず。

feature の依存関係

ところで、もし feature が他のクレートに依存していたらどうするか? 例えば "greetings" のみが libc に依存している場合を考えてみる。

foo/Cargo.toml に次のように書くと libc は常にダウンロードされてしまう。

# foo/Cargo.toml

...

[dependencies]
libc = "0.2"

...

これは良くない。 optional にするべき。

# foo/Cargo.toml

...

[dependencies]
libc = { version = "0.2", optional = true }

[featres]
greetings = ["libc"]

...

こすると libc は greetings が有効な時にだけダウンロードされる。

default features

特定の feature をデフォルトにしたい場合、default feature を作る事ができる。

foo/Cargo.toml を次のように編集。

# foo/Cargo.toml

...

[features]
default = ["greetings"]
greetings = ["libc"]

...

これで "greetings" はデフォルトになったので使う時に明記する必要がなくなった。 bar/Cargo.toml の依存関係は以下で十分。

# bar/Cargo.toml

...

[dependencies]
foo = { version = "^0", path = "../foo" }

...

もし、あえてデフォルトの features を無効にしたい場合、次のようにすれば良い。 (もちろん、ビルドは通らなくなる。)

# bar/Cargo.toml

...

[dependencies]
foo = { version = "^0", path = "../foo", default-features = false }

...

結論

個人的にはこの仕組みを気に入った。 依存関係が少ないほど、コンパイル時間やバグが減る。

Rust は C/C++ に比べて複雑だけど、よく考えられていると思う。 時間をかけて勉強する価値がある。