Conditional compilation and rustdoc intra-doc links

Hi all documentation enthusiasts,

Now that Rust 1.48 is out, I've started using intra-doc links in my projects. So I wrote things like this:

//! # Cargo Features
//!
//! The textwrap library has two optional features:
//!
//! * `terminal_size`: enables automatic detection of the terminal
//!   width via the [terminal_size] crate.

This works great if I generate the documentation using cargo doc --all-features. However, if I leave out the flag, I get a warning since there is no terminal_size crate then:

warning: unresolved link to `terminal_size`
  --> src/lib.rs:79:22
   |
79 | //!   width via the [terminal_size] crate. See the
   |                      ^^^^^^^^^^^^^ no item named `terminal_size` in scope
   |
   = note: `#[warn(broken_intra_doc_links)]` on by default
   = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`

Luckily, I can put

[package.metadata.docs.rs]
all-features = true

into my Cargo.toml file to have docs.rs render things fine — is there a similar way to have cargo doc work out of the box when I run it locally?

I tried playing with a .cargo/config.toml file where I tried overriding cargo doc and tried setting the rustdoc flags:

[alias]
doc = ["doc", "--all-features"]

[build]
rustdocflags = ["--cfg", 'feature="terminal_size"']

Neither worked: you're unfortunately not allowed to override builtin commands with the [alias] section and changing build.rustdocflags didn't seem to have any effect — though this might very well be me who doesn't understand what to pass here :slight_smile:

Any advice? Note that I'm not just linking to the optional dependencies, I'm also sometimes referring to conditionally available methods in my own code.

I don't think so. In Tokio we just tell people to use a specific command.

RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features

and we too use a Cargo.toml declaration:

[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]

Hej Alice, thanks for confirming that I'm not the only one who've run into this.

Since it's easy enough to make docs.rs render with all features, it's of course just a minor annoyance that cargo doc doesn't work warning free out of the box.

Yeah, I can understand why that's the case, but in this instance it would be nice to be able to "soft-disable" (or attach a warning to) a built-in command:

[alias]
docs = ["doc", "--all-features"]
doc = { warn = "Use `cargo docs` instead" }

And indeed the cargo docs alias would work for you.

The reason this does not work is because Cargo feature flags play two roles:

  • setting the rustc --cfg flag of the form feature="<feature_name>", which can indeed be emulated by tweaking the flags passed to rustc directly, by a .cargo/config file or a build.rs script;

  • affect the dependency tree resolution, to decide which external crates are available. This happens before build.rs script shenanigans, and it ignores direct rustc flags, even those of the form feature="<feature_name>"


Back to the topic at hand, intra-doc-links is especially nice and useful for internal links; if you want to refer to an external crate then using a hard-coded https://docs.rs/<external_crate_name> will, in practice, be almost as useful (the only difference being somebody working in --offline mode who happened to have pre-downloaded that external_crate; I don't think that supporting that use case warrants all these cumbersome changes for everybody).

So I'd recommend that, instead, you simply append the following line to your docstring.

/// [terminal_size]: https://docs.rs/terminal_size
  • if you really wanted to go the extra mile (but know that it may confuse rustdoc a bit), you could:

    #[cfg_attr(not(feature = "terminal_size"),
        doc = r#" [terminal_size]: https://docs.rs/terminal_size",
    )]
    

I went way overboard and dynamically generate a table telling you what features you have active, only linking to their dependencies when they're active:

//!  Feature | Type
//! ---------|------
#![cfg_attr(
    feature = "futures-io",
    doc = "[`futures-io`](crate::futures) | [`futures::io::AsyncBufRead`](futures_io::AsyncBufRead), [`futures::io::AsyncWrite`](futures_io::AsyncWrite)"
)]
#![cfg_attr(
    not(feature = "futures-io"),
    doc = "`futures-io` (*inactive*) | `futures::io::AsyncBufRead`, `futures::io::AsyncWrite`"
)]
...
#![cfg_attr(
    feature = "tokio-03",
    doc = "[`tokio-03`](crate::tokio_03) | [`tokio::io::AsyncBufRead`](::tokio_03::io::AsyncBufRead), [`tokio::io::AsyncWrite`](::tokio_03::io::AsyncWrite)"
)]
#![cfg_attr(
    not(feature = "tokio-03"),
    doc = "`tokio-03` (*inactive*) | `tokio::io::AsyncBufRead`, `tokio::io::AsyncWrite`"
)]
//!

I also wrote a macro that allows easily dynamically including sections of docs based on cfg, mostly to support excluding examples that don't currently apply (though I never got round to merging this):

https://github.com/Nemo157/bs58-rs/commit/847a4555fa5f654a4ae1c6724cad3d60e973b394#diff-5bcac9d065ac0e857e3f34b17fe1e84b5849f674851fa77670b08600d87c768aR301-R319

Thanks for the great explanations!

Yes, I agree completely, simple links are much better for external crates. I realize now that I picked a poor example — I also have a function which is only defined if a Cargo feature is enabled. It seems that there isn't a clean or easy work-around, so I'll just let the link remain undefined in this case.

Wow, that's pretty cool! A bit overkill, but nevertheless very cool :smiley:

Thank you both for the help!