- Feature Name: api_doc_frontpage_styleguide
- Start Date: 2016-07-25
- RFC PR: (leave this empty)
- Rust Issue: (leave this empty)
This RFC is a companion to RFC 1574 concerning API documentation conventions. This time we focus on the format and style of the "front page" of documentation as generated by rustdoc. The goal of this RFC is to outline a clear presentation style for crate documentation that is helpful to new users of a crate. We also clarify the relationship between the rustdoc front page and the project's readme. These are the guidelines we will be using for the rust-lang crates, but are also meant to help guide community crate authors.
A user's first experience with a crate is often the front page of the crate's documentation. Taken together with the API documentation and readme, the front page is a critical piece of understanding a crate's purpose, how to use it, and its limitations. This RFC furthers our roadmap goal of providing easy access to high quality crates. A crate may be otherwise high quality but shoddy front page documentation can lead people to pass over it.
The front page of the crate should cover three main points. In this RFC, we cover each in turn.
The purpose of the introduction is to help a user quickly figure out whether this is the right crate for them.
In designing the introduction, consider a user searching for a crate to fulfill some use case, landing on the front page of several similar crates, and trying to determine which one to pursue further. The introduction is responsible for concisely conveying what the crate does and why somebody might want to use it. By the end of the introduction the reader should either be confident that this is not what they were looking for, or eager to read the example code and capabilities that follow the introduction.
Here is an example of a decent introduction from the log
crate:
A lightweight logging facade.
A logging facade provides a single logging API that abstracts over the actual logging implementation. Libraries can use the logging API provided by this crate, and the consumer of those libraries can choose the logging framework that is most suitable for its use case.
If no logging implementation is selected, the facade falls back to a "noop" implementation that ignores all log messages. The overhead in this case is very small - just an integer load, comparison and jump.
A log request consists of a target, a level, and a body. A target is a string which defaults to the module path of the location of the log request, though that default may be overridden. Logger implementations typically use the target to filter requests based on some user configuration.
Based on the introduction, this crate is not for somebody hoping to compute a logarithm or a Laplacian of Gaussian. If you are writing a library that wants to log messages in a way that is governed by the user, or if you are an application needing to tap into the log output of your dependencies, you should keep reading.
Another example from the rand
crate:
Utilities for random number generation
The key functions are
random()
andRng::gen()
. These are polymorphic and so can be used to generate any type that implementsRand
. Type inference means that often a simple call torand::random()
orrng.gen()
will suffice, but sometimes an annotation is required, e.g.rand::random::<f64>()
.See the
distributions
submodule for sampling random numbers from distributions like normal and exponential.
In this introduction it would be valuable to touch on a fundamental question that many people evaluating a random number library will need to know: where does the randomness come from? Is it from the operating system? Is there a pseudorandom number generator? Is it cryptographically secure?
The purpose of the first example is to help the reader imagine themselves as a user of your crate. It gives them a sense of how high level or low level the crate's primary abstractions are and how some of the verbally described features play out in practice.
Ideally this example should only demonstrate your crate and core Rust
functionality. Avoid large examples with lots of features; those belong in the
examples
directory.
Let's look at a good first example from the log
crate. Notice that the
author has focused on getting started, showing how to import and use the crate,
and a few simple uses of common functionality.
#[macro_use]
extern crate log;
pub fn shave_the_yak(yak: &Yak) {
info!(target: "yak_events", "Commencing yak shaving for {:?}", yak);
loop {
match find_a_razor() {
Ok(razor) => {
info!("Razor located: {}", razor);
yak.shave(razor);
break;
}
Err(err) => {
warn!("Unable to locate a razor: {}, retrying", err);
}
}
}
}
The core capabilities of a crate are the main reason people will use your crate. In the next section, you can document what each of these core capabilities are and how to use them. For example, in a crate about random numbers, you may have sections about: generating random numbers, fitting numbers to statistical distributions, use in cryptography, thread-local random numbers, and so on.
It is helpful to introduce and give a clear description for each capability separately to help your readers understand each concept individually before they begin to combine capabilities.
Here is a good example from the rand
crate:
There is built-in support for a RNG associated with each thread stored in thread-local storage. This RNG can be accessed via
thread_rng
, or used implicitly viarandom
. This RNG is normally randomly seeded from an operating-system source of randomness, e.g./dev/urandom
on Unix systems, and will automatically reseed itself from this source after generating 32 KiB of random data.An application that requires an entropy source for cryptographic purposes must use
OsRng
, which reads randomness from the source that the operating system provides (e.g./dev/urandom
on Unixes orCryptGenRandom()
on Windows). The other random number generators provided by this module are not suitable for such purposes.
Sample code for each capability should be as simple as possible. In the rand
crate, thread safe RNG is demonstrated in 5 lines of code:
use rand::Rng;
let mut rng = rand::thread_rng();
if rng.gen() { // random bool
println!("i32: {}, u32: {}", rng.gen::<i32>(), rng.gen::<u32>());
}
After this example, a user should immediately be able to use a thread safe random number generator in their own program.
Examples may go inline under each capability, or all together in an Examples section.
More complex source examples should be included as a separate program in the
examples
directory. Be wary of putting large examples in your doc where users
will have to read past the code to get the beginning of your API documentation.
You should avoid examples that require understanding of external crates unless it is necessary. As a hypothetical example, your logging crate should not require the user to have experience with Diesel. It is okay to leave these integrations for the reader to put together themselves.
This is an unresolved question.
This RFC will be linked from the Rust API guidelines in a guideline dedicated to front page documentation.
As part of the Libz Blitz, the community and the libs team will evaluate high-profile crates against this guideline and file issues where improvement is required. With front page documentation improving across the ecosystem, users will learn to depend on it and will notice and complain about crates that fall short. Those crates can improve their front page experience by using this RFC as guidance.
A possible drawback of this approach is that it risks over-specifying a format that is ill-served for a particular crate. For example, a crate with a lot of moving parts may need to use more complex examples because smaller examples lose educational value or overall impact.
Serde, Tokio, Diesel, and other large projects are factored across many individual crates. We may provide explicit guidance about how to handle front page documentation in this situation, beyond just applying this RFC to each crate individually.
We may recommend an upper bound on the size of example code that belongs in the
front page documentation. For example we may say that anything over 40 lines
probably belongs in its own dedicated file in the examples
directory.
We may recommend only one or two examples of the most basic sort, and links to
examples
or a website for anything more thorough. During your first
introduction to a crate, links are just as edifying as inline examples. During
your next 1024 times referring to the rustdoc as reference, you are not forced
to scroll past the no longer useful example code.
We may recommend a section or just a lone code block that gives the basic mechanics of depending on a crate.
[build-dependencies]
gcc = "0.3"
This saves users the effort of:
- Remembering the crate name (crate is gcc, repository is gcc-rs);
- Figuring out what the latest version is;
- Knowing which section to put the dependency into.
Browsing from a rustdoc front page to any of these other references is currently obnoxious. On docs.rs they provide these links in a sidebar.
Some crates provide manual cross-links.
We may recommend that crates do something like this in their frontpage. However, I believe that this should be a feature request for rustdoc instead.
What goes in the readme? Do we duplicate all or most of the front page doc in the readme?
There are some things that have traditionally gone in the readme but not the front page doc:
- Requisite system libraries
- Code of conduct
- Instructions for developers
- CI status
- Semantic versioning philosophy
- Supported OS and Rust versions
- Comparison with other crates
- Benchmark data
- License boilerplate
Is there a good reason for this distinction?
Here are some projects with very different readmes and front page docs:
serde
readme and front pagerocket
readme and front pagetokio-core
readme and front pagedocopt
readme and front page
Here are some projects with somewhat similar readmes and front page docs:
clap
readme and front page
Here are some projects with practically identical readmes and front page docs:
serde_json
readme and front page
How best to document a crate's limitations.
Where documentation for features goes. Serde has this on the website and in Cargo.toml but not in rustdoc.
This crate is licensed under either of Apache License, Version 2.0, (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0) or MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT) at your option. Unless you explicitly state otherwise, any contribution intentionally submitted bla bla bla.
Does this need to be in the readme and/or the rustdoc?
The Serde readme has the following stanza. It is designed to catch new users who may silently struggle and then abandon the crate without getting help.
Serde developers live in the #serde channel on
irc.mozilla.org
. The #rust channel is also a good resource with generally faster response time but less specific knowledge about Serde. If IRC is not your thing or you don't get a good response, we are happy to respond to GitHub issues as well.
Does this belong in the readme and/or the rustdoc?
Migration from v1 to v2 is part of in the slog
front page documentation. Is
this an appropriate place?