Docs.rs crate features display

As of now, Docs.rs does not display the available features that a crate offers. I'd like to change this, but there's some design work that needs to be done before hand for things to go smoothly, with these being the largest concerns

  • Opt-in/opt-out functionality
  • Displaying optional dependencies
  • The final UI displayed to users

Introduction

Crates have features. These are options that can be set within a crate's Cargo.toml that enable and disable dependencies and all-around functionality of a crate. As an example, winapi gates each Windows module behind a feature so that you only enable what you need to use (hopefully allowing a smaller dependency footprint and faster compile times). Docs.rs displaying these features has been a requested feature (of Docs.rs) for a while now, since it'll allow users to easily see what they have to choose from without having to dive into source code. Note that any changes made only apply after they've been deployed, we could potentially re-parse old Cargo.toml files, but that'd be lots of extra work for the server to do while delivering content to the user.

Opt-in/opt-out functionality

Docs.rs uses a number of keys in the package.metadata section of crates' Cargo.toml, and I think that's a great place to store this information, an excluded-features key of some sort probably being the best. This key would allow you to select features for Docs.rs to not display to users, so in this example Cargo.toml

[features]
my-public-feature = ["my-public-feature"]
my-secret-feature = []

[package.metadata.docs.rs]
excluded-features = ["my-secret-feature"]

The only things displayed to users would be that there's the my-public-feature that activates something (similar to how rustdoc handles private/hidden struct fields or enum variants), creating a user-sided display to the effect of

Features Activates
my-public-feature ...some features omitted

Similarly we could include an include-features key that worked inversely, disabling all features except the ones specifically listed, but I think that's a less useful tool.

These inclusion/exclusion keys are useful for two main reasons, first being that many crates have something to the effect of a "benchmarking feature" that exports otherwise private items for benchmarking, and the second is for cases where users have many generated or detected features, but there's a preferred subset of features that users should be using.

Displaying optional dependencies

Cargo creates a feature for each optional dependency in your Cargo.toml, so with this manifest

serde = { version = "*", optional = true }

The serde feature is implicitly created. This could potentially create a lot of spam in crates with many optional deps, but automatically omitting all optional dependencies could also lead to an information loss, like in the serde example above (where theoretically when serde is enabled, the crate gains serde support).

There's a few ways to address this, either leaving it up to maintainers to select important information via excluding features or creating an entirely separate table for optional dependency features. In the latter case something like this could be expected

[features]
give-me-serde = ["serde"]
derive-me-serde = ["serde/derive"]
some-other-feature = []

serde = { version = "1.0.115", optional = true }
Feature Activates
give-me-serde serde
derive-me-serde serde/derive
some-other-feature none
Optional dependencies Feature name
serde v1.0.115 serde

The distinction between the dependency's name & version and the name of the feature is needed because Cargo allows renaming dependencies, so the following manifest would be valid and create the following features display

serde = { version = "1.0.115", optional = true }
middle_serde = { version = "0.6.7", package = "serde", optional = true }
old_serde = { version = "0.0.0", package = "serde", optional = true }
Optional dependencies Feature name
serde v1.0.115 serde
serde v0.6.7 middle_serde
serde v0.0.0 old_serde

User-sided UI

I'm not a very design-inclined person, so the problems of layout and making things pretty are fairly lost on me. Whichever of the approaches to displayed information is taken, it's clear that it'll be a sizeable amount of information, which all needs to go somewhere. The best ideas I have are either below a crate's readme or as a separate tab alongside Documentation, Crate, Source and Builds that's entirely dedicated to displaying features.

We could make a summarized version of features listed in the crate dropdown, but it's already quite full and I think that a link to wherever features were displayed has the same effect.

Possibilities and extensions

Every crate has a default feature set, whether implicitly created or explicitly created using the features.default key in their Cargo.toml. The default features of a crate are particularly important, as it tells you what will be enabled by default. As such the default features should have attention called to them, whether by highlighting them in a special way, hoisting them to the top of the features list or both.

Another generally useful thing to display would be what the current docs are being built with. Crates can set the features that Docs.rs builds them with by using the metadata section, which can mean that things exclusive to a feature may or may not be displayed depending on what's set. This would help with that by telling the users exactly what features are being shown to them, and could be included as part of the features section, creating something to this effect:

These docs were built with the following features: give-me-serde

Feature Activates
give-me-serde serde
7 Likes

For prior art, lib.rs incorporates features in the dependency list, e.g. rand:

image

4 Likes

Features are a bit weird. In making of lib.rs I've noticed that there's an implicit feature for every optional dependency, but lots of crates don't support these implicit features, and instead expect users to use explicit feature flags.

So for lib.rs I've invented a rule that if an optional dependency is enabled by any explicit feature, then I don't show the implicit feature flag.

e.g. if there's:

serde = { optional = true }

[features]
foo = []

then I consider the crate to have two features: serde and foo. But if it has:

serde = { optional = true }

[features]
with_serialization = ["serde"]

then I only show with_serialization feature, and don't show the serde feature.

8 Likes

For hidden features, I suggest excluding features starting with an underscore. Crates already use flags like __private_dont_use informally. Underscore prefix is a naming pattern in Rust already. Defaulting to this would make it "just work" for crates with no extra config.

9 Likes

That sounds pretty reasonable, that plus the ability to manually include/ignore covers pretty much everything

I use the Rust search extension browser addon for this. Among other things, it adds a drop-down to docs.rs containing all features.

I've been making pretty extensive use of doc_cfg for this purpose (which works on docs.rs today), and having done such, I think the way it works (by annotating your code) is really valuable for documenting features.

Here's an example:

https://docs.rs/elliptic-curve/0.5.0/elliptic_curve/

It's a bit tricky to use right now, since it's unstable, underdocumented, and requires a nightly-only feature. It could also probably be improved quite a bit to do things like index which types are gated under which features, and perhaps be more automatic as opposed to requiring manual annotations.

But that said, it seems really nice for documenting features and feature combinations.

7 Likes

Unfortunately doc_cfg isn’t under our control, documentation generation is done by rustdoc

Aah sorry, I guess you're talking about the project overview page. My bad!

Yes please do this.