November 19, 2020·5 min read·engineeringrustrustdocdocumentation

Rust 1.48 resolves documentation links through Rust names

Intra-doc links replace fragile output paths with item names. Cross-crate reexports made the implementation harder than ordinary Markdown linking suggests.

A documentation link to another Rust type should describe the type, not the HTML file rustdoc happens to generate for it. Handwritten relative paths become fragile when a public item is reexported and its documentation appears in another location. Rust 1.48 stabilizes intra-doc links so rustdoc can resolve item names and generate the appropriate destination itself.

The release announcement presents this as a documentation improvement, but its history reaches into compiler name resolution. Markdown alone cannot know which Rust item a name denotes. The tool needs the scope where the documentation originated, including when another crate reexports the documented item.

I value documentation that makes a library's relationships easy to follow. A semantic link is useful because the author states the relationship once and lets the compiler's naming information supply its location. Requiring each author to maintain generated output paths adds work unrelated to explaining the API.

The announcement demonstrates two modules, each containing a public struct. A comment on one refers to the other. Previously, a clickable link needed a path such as a neighboring module's struct HTML file. Relative links helped offline documentation work, but they still encoded rustdoc's output arrangement.

Reexporting the struct at the crate root changes where its documentation can appear. A relative link correct inside its original module can then be wrong in the root presentation. Editing the link for the reexport can break the original page. The problem is therefore not merely careless path maintenance.

An intra-doc link instead points at a Rust path. Rustdoc resolves the target and chooses the URL for the rendered location. The comment can give the link a short label while identifying its full target explicitly.

pub mod records {
    /// A record can be processed by [`Reader`](crate::readers::Reader).
    pub struct Record;
}

pub mod readers {
    /// Reads a [`crate::records::Record`].
    pub struct Reader;
}

The first comment displays the short name while supplying a crate-relative target. The second uses the Rust path itself as the visible label. Both forms identify the item through the source naming model rather than through the generated filename.

Cross-crate reexports needed the originating scope

The rustdoc team's account of stabilization explains why the work took several years. Early implementations could resolve links in a crate's own documentation. Reexporting an item from a dependency exposed the missing information. The local scope relevant to a link belonged to the dependency, not the crate currently being documented.

Rustdoc renders a reexport in place and parses its documentation there. A short reference to a function in the original module cannot safely resolve against whatever happens to be in scope at the new location. The implementation needs the original item's context.

The team initially believed supplying that context required substantial additional compiler work. Meanwhile, the compiler gained facilities to store and query dependency resolution scopes. The stabilization account describes recognizing that this formerly missing capability had become available. A blocker can persist in a project's understanding after the underlying dependency has changed.

Using cross-crate identities then uncovered interactions with rustdoc's handling of function bodies. A transformation used to simplify documentation compilation removed constructs whose identities the new code tried to query. The resulting crash showed that obtaining scope information also depended on which compiler transformations rustdoc performed.

Documentation compilation has a different purpose

The problematic transformation replaced function bodies so rustdoc could avoid errors inside platform-specific implementations. A documentation build may expose both Windows and Unix APIs even though their implementation details cannot all compile normally on the current host. Rustdoc needs declarations and documentation without executing every ordinary body-checking path.

The replacement approach described by the team stops type checking function bodies instead of rewriting them into bodies designed to pass checking. That avoids creating inconsistent identities for constructs removed by the transformation. It also better matches what documentation generation actually needs.

Async functions exposed another complication. Their hidden return types could still require body inference, reintroducing checking the documentation build wanted to avoid. The team's history describes an additional workaround for that case. This is why a short link syntax rests on changes beyond the Markdown parser.

The stable user-facing result is simpler than those internals. An author writes a target name, and rustdoc has enough context to connect it with the right item. The history explains the reliability requirement behind stabilization, especially for libraries whose public APIs include reexports.

I find the reexport case the most useful test of the feature's purpose. Documentation must remain navigable through the public API a reader encounters. A link that works only at the original definition would leave the main maintenance problem unresolved.

The release also illustrates why precise API descriptions matter with the new conversion from a vector into an array of a selected length. TryFrom can fail when the lengths differ. The announcement demonstrates a panic by calling expect on that failed result, but the conversion itself returns a result that the caller can handle. A documentation link to the conversion API should help a reader follow that distinction. Linking to a named item preserves the relationship even when the generated page moves, while the surrounding prose remains responsible for explaining which operation fails and which operation chooses to panic.

Search aliases address terminology instead of location

Rust 1.48 also stabilizes documentation search aliases through #[doc(alias = "...")]. An item can appear in search results under another term without changing its Rust name. The announcement suggests wrapper libraries for C APIs as a useful case, because users may know the original C function name.

#[doc(alias = "read_record")]
pub fn read() {}

The alias adds a search term. It does not introduce a callable Rust function named read_record, nor does it change ordinary name resolution. That distinction lets documentation support familiar terminology without adding compatibility names to the code API.

Intra-doc links and aliases therefore solve different navigation problems. Links connect a statement in a comment to its intended Rust item across rendered locations. Aliases help a reader find that item using terminology they already know. Both keep documentation relationships in source while rustdoc handles the generated navigation that depends on them.