July 07, 2020

English | 日本語

Adding Features to a Rust Crate (English)

Suppose you are creating a new Rust library crate.

Now, you like to export function “hello()” if the library user enables the feature “greetings”. How do you do it?

Crate publicating function “hello()”

Let’s create a local package and call the function from another binary at first.

Library crate “foo”

Type as follows in the shell.

# shell

$ cargo new --lib foo

Then edit foo/src/lib.rs as follows

// foo/src/lib.rs

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

Binary “bar” calling “foo::hello()”

Type as follows in the shell.

# shell

$ cargo new --bin bar

Then edit bar/src/main.rs as follows

// bar/src/main.rs

extern crate foo;

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

Finary, add the following line in [dependencies] section in bar/Cargo.toml

# bar/Cargo.toml

...

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

...

Type as follows in the shell. Binary bar will display “Hello world.”

# shell

$ (cd bar; cargo run)

Making “hello()” feature “greetings”

Function “foo::hello()” works well. Then, make it feature function.

First, creating the feature in crates foo. Make “[features]” section in foo/Cargo.toml and add “greetings” there.

# foo/Cargo.toml

...

[features]
greetings = []

...

(The empty brackets indicates the feature doesn’t require any additional dependency. I write the details in later.)

Next, move “hello()” to the new feature. Add line ‘#[cfg(feature = “greetings”)]’ to foo/src/lib.rs.

// foo/src/lib.rs

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

Now, the compiling bar will fail because it has not enabled the feature yet.

Update the dependencies of bar/Cargo.toml as follows.

// bar/Cargo.toml

...

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

...

If you don’t like the long line, you can create “[dependencies.foo]” instead.

# bar/Cargo.toml

...

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

...

Build will be pass.

feature dependencies

By the way, if the feature depends on another crate, what should we do? For example, crate libc is necessary only when “greetings” is enabled.

If you add the following lines to foo/Cargo.toml, libc will always be downloaded.

# foo/Cargo.toml

...

[dependencies]
libc = "0.2"

...

This is not cool. The dependency should be optional.

# foo/Cargo.toml

...

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

[featres]
greetings = ["libc"]

...

Then libc is downloaded only if greetings is enabled.

default features

If you like to make some features default, you can add feature default.

Edit section features in foo/Cargo.toml as follows.

# foo/Cargo.toml

...

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

...

Then greetings is default feature and it is not necessary to specify to use. It is enough to write dependencies in bar/Cargo.toml.

# bar/Cargo.toml

...

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

...

If you dare to disable the default features, write as follows. (Then you can’t build bar, of cause.)

# bar/Cargo.toml

...

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

...

Conclusion

I like this Rust system. Few dependencies decrease the compile-time and bugs.

I think Rust is complicated compared to C/C++, however, it is very smart. It is worth to study.