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"]
1 Like

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",
    )]
    
1 Like

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):

3 Likes

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!

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.