Skip to content

Instantly share code, notes, and snippets.

@peschkaj
Last active June 7, 2016 14:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save peschkaj/291a551f0ab1a395ca14b08e8c9ef935 to your computer and use it in GitHub Desktop.
Save peschkaj/291a551f0ab1a395ca14b08e8c9ef935 to your computer and use it in GitHub Desktop.

Rust Documentation Guidelines

Introduction text to the crate

The first thing potential users of your crates will see is the introductory summary. This section is a good place to introduce the what and the why the crate. A good introduction is concise but also provides enough information for the reader to understand the purpose of the crate. In addition, a good introduction provides a learning path for the user as they read the rest of the documentation.

Here's an example of a good 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.

There are several things that make this a good introduction section:

  • A concise explanation of the crate's purpose.
  • The summary describes how the crate behaves in 3 short paragraphs.
  • Even if you don't want to read the rest of the README, you have enough to get started.
  • Readable by a layman, no jargon is introduced in the first few paragraphs.

What do we mean by "no jargon is introduced in the first few paragraphs"? Using the rand crate as an example - the crate's initial documentation probably should not include a discussion of cryptographically secure random number generators since its primary purpose, for most Rust developers, will be to product any random number. This information should instead be provided in a separate section that specifically discuss cryptographically secure random number generation.

In general - it's good to give a developer a place to easily learn what your crate is about. Once they have the general idea, you can dive into more details that are specific to your crate, including jargon that would be common with its use.

Now that we've seen an example of a good introduction, let's take a look at one that needs a little work. Don't worry, we're picking on ourselves here ;)

Here's an example of a rougher introduction from the flaker crate:

A flake is a 128-bit, k-ordered ID - it's real time ordered and stored in a way that sorts lexically. Flaker is derived from Boundary's flake implementation and the author's previous work on Rustflakes - a similar tool for .NET.

Basically - it's an ordered ID generation service.

Rather than rely on a central data storage repository for generating IDs, developers can implement an ID generator at the source of data using a flaker.

Some of the things that can be improved in this example:

  • Immediately uses confusing jargon.
  • After the summary, the documentation discusses data structures and API calls with no background or examples.

First Example

The first example should provide an easily understood example to help a developer understand how they can start making use of your crate. Ideally, this example should only demonstrate your crate and core Rust functionality. Avoid exercises in yak shaving.

Here's a sample of a good first example of a 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);
            }
        }
    }
}

Crate Capabilities

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 crypto, thread-local random numbers, and so on.

It's 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's a good example from the 'rand' crate:

Thread-local RNG

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 via random. 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.

Cryptographic security

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 or CryptGenRandom() on Windows). The other random number generators provided by this module are not suitable for such purposes.

Source Examples

Sample code for each crate capability should be as simple as possible and side effect free. In the rand crate, thread safe RNG is demonstrated 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.

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 also avoid examples that require understanding of external crates unless it's absolutely necessary. As a hypothetical example - your logging crate should not require the user to have experience with diesel. It's okay to leave these capabilities as an exercise left to the reader.

API Documentation

Each of the functions (including macros) in your public API should be documented with:

  • A short description of their use
  • A small sample showing their use (this needn't be a full example, but one that gives a good idea of its use)

Documentation for types, including structs and traits, can be as simple as some descriptive text in the rustdoc comments. Examples here may be helpful but aren't as necessary as documenting the public functions and macros.

If you're good about documenting the basics, a lot of this API documentation is provided for you by rustdoc and cargo doc. Although these tools don't currently provide documentation hosting, this is relatively simple to set up using Travis or a different CI tool. See Steve Klabnik's post Automatically Update GitHub Pages with Travis.

For details about Rust's built in documentation, refer to the documentation chapter of the book.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment