Skip to content
This repository has been archived by the owner on Nov 5, 2020. It is now read-only.

A place to contemplate the Rust package manager's Features feature

Notifications You must be signed in to change notification settings

ludumipsum/cargo-features-garden

Repository files navigation

Intro

The Cargo Features Garden is a collection of examples demonstrating the sometimes surprising behavior of Cargo Features. The intent is to take a clear but critical look at how Cargo Features work under the hood and how the Cargo.toml manifest may contribute to misunderstandings.

Structure

The directories in this repository aim to be self-descriptive, and contain Cargo Workspaces or contain a series of directories that contain workspaces. Many of the crates in these repository will not compile and this is as intended. If running cargo build -- without specifying a --package -- in any of these workspaces does not yield an error, that is purely by coincidence. Each of these directories contains a README.md that will discuss the contents, the crates intended to be built, and the expected results.

It will be common for directories in a workspace to be named something like 1_lib-a, 1_lib-b, 2_exe-a. In this case, the crates will be named lib-a, lib-b, and exe-a, and the numerical prefix will suggest the crate's level in the implied dependency tree. lib-a and lib-b are at the lowest level, so neither depend on any crate in the workspace. exe-a is at the second level, probably because it depends on one or both of the libraries.

Table of Contents

This might also be a suggested reading order. If so, the suggestion is very loose.

A relatively straight-forward demonstration of the possibly surprising nature of feature unification.

An exploration of union'd feature flags focusing on no_std. (Well, it builds into a discussion of no_std. Spoilers, I guess.)

An exploration of union'd feature flags focusing on deeply nested, target-specific features.

An exploration of possible methods to encode mutually exclusive features into crates.

Ongoing Work

Below is a list of active, interrelated issues relating to Cargo Features, brief descriptions of them, and links to salient GitHub issues (mostly in the rust-lang/cargo Features issue list.


Cargo Features are Always Unified

When you include a crate in more than one context -- in both [dev-dependencies] and [build-dependencies], behind [target.'cfg(...)'.dependencies] expressions, transitively from other dependencies, etc. -- all of the features activated across all instances of the dependency are union'd prior to a build. That means you could write something like,

[dependencies]
some-lib = *

[target.'cfg(false)'.dev-dependencies]
some-lib = { version = "*", features = ["broken-experimental"] }

expecting some-lib to never have the broken-experimental feature activated. In reality, no mater what target or profile this crate is built with, the broken-experimental feature will always be active on some-lib.

Examples

Issues

The rust-lang issues and discussions surrounding this behavior can be broken down into three rough categories;


Activating a Feature that Activates a Feature on an Optional Dependency also Activates the Optional Dependency

This one is somewhat self-explanatory, if you can parse the above title. If you have an optional dependency,

optional-lib = {version = "*", optional = true }

... and a feature that enables a feature on that optional dependency,

some-feature = ["optional-lib/foo"]

... activating that feature also activates the optional dependency. Which means that this,

# Activate the dependency on `optional-lib`, and the feature `optional-lib/foo`.
my-lib = { features = ["optional-lib", "some-feature"] }

... is the same as this,

# Activate `optional-lib/foo` and imply the dependency on `optional-lib`.
my-lib = { features = ["some-feature"] }

Issues

Additional duplicates w/o meaningful conversation; rust-lang/cargo#5023, rust-lang/cargo#6658, rust-lang/cargo#7259


Mutually Exclusive Features

When authoring code that needs to be platform-aware, it seems to be a common pattern to homogenize system-level operations by implementing similarly named modules, and useing them like,

#[cfg(unix)]
pub use crate::sys::unix as sys;
#[cfg(windows)]
pub use crate::sys::windows as sys;
#[cfg(target_arch = "wasm32")]
pub use crate::sys::wasm as sys;

This can also be a useful pattern for selecting a specific implementation of a more general operation. For example flate2 defaults to using miniz as a compression/decompression library, but can optionally be configured to use zlib, or miniz_oxide. Building flate2 with cargo build --features "zlib rust_backend" throws no errors, but it's unclear (to me, at least) which backend will be used in that case.

It seems a reasonable ask to for Cargo to provide a mechanism for enforcing mutually exclusive features.

Examples

Issues & Discussions


Workspaces do not Play Well with Features

I need to do more research here to dig into the specifics. Generally, Features aren't great at multiple contexts, and Workspaces aren't well polished. The interaction between these two features results in a less than stellar user experience.

Issues

Additional duplicates w/o meaningful conversation; rust-lang/cargo#5251

About

A place to contemplate the Rust package manager's Features feature

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages