Documentation is an important part of software engineering. Instead of simply writing up some functions and chaining them together on a hunch, we like to promote writing reusable and readable code. Part of this is also writing sensible documentation—which, in ideal cases, can be rendered into other formats such as HTML or PDF. As many languages do by default, Rust provides a tool and language support as well: rustdoc.
Documenting your code
Getting ready
Failing our high standards of software engineering, we did not document the code from the last recipe! To change that, let's load a project with code to be documented (such as the previous recipe, Writing tests and benchmarks) into an editor.
How to do it...
Compile your code comments to a shiny HTML in just a few steps:
- Rust's docstrings (strings that explicitly are documentation to be rendered) are denoted by /// (instead of the regular //). Within these sections, markdown—a shorthand language for HTML—can be used to create full documentation. Let's add the following before the List<T> declaration:
///
/// A singly-linked list, with nodes allocated on the heap using
///`Rc`s and `RefCell`s. Here's an image illustrating a linked list:
///
///
/// ![](https://upload.wikimedia.org/wikipedia/commons/6/6d/Singly-
///linked-list.svg)
///
/// *Found on https://en.wikipedia.org/wiki/Linked_list*
///
/// # Usage
///
/// ```
/// let list = List::new_empty();
/// ```
///
#[derive(Clone)]
pub struct List<T> where T: Sized + Clone {
[...]
- This makes the code a lot more verbose, but is this worth it? Let's see with cargo doc, a subcommand that runs rustdoc on the code and outputs HTML in the target/doc directory of the project. When opened in a browser, the target/doc/testing/index.html page shows the following (and more):
- Great, let's add more documentation in the code. There are even special sections that are recognized by the compiler (by convention):
///
/// Appends a node to the list at the end.
///
///
/// # Panics
///
/// This never panics (probably).
///
/// # Safety
///
/// No unsafe code was used.
///
/// # Example
///
/// ```
/// use testing::List;
///
/// let mut list = List::new_empty();
/// list.append(10);
/// ```
///
pub fn append(&mut self, value: T) {
[...]
- The /// comments add documentation for expressions that follow it. This is going to be a problem for modules: should we put the documentation outside of the current module? No. Not only will this make the maintainers confused, but it also has a limit. Let's use //! to document the module from within:
//!
//! A simple singly-linked list for the Rust-Cookbook by Packt
//! Publishing.
//!
//! Recipes covered in this module:
//! - Documenting your code
//! - Testing your documentation
//! - Writing tests and benchmarks
//!
- A quick cargo doc run reveals whether it worked:
- While there is some benefit in having similar-looking documentation in any Rust project, corporate marketing often likes to have things such as logos or a custom favicon to stand out. rustdoc supports that with attributes on the module level—they can be added right below the module documentation (note: this is the logo of my Rust blog, https://blog.x5ff.xyz):
#![doc(html_logo_url = "https://blog.x5ff.xyz/img/main/logo.png")]
- To see whether it worked, let's run cargo doc again:
Now, let's go behind the scenes to understand the code better.
How it works...
Markdown is a great language that allows for creating formatted documentation quickly. However, feature support is typically tricky, so check out Rust's RFC for supported formatting (https://github.com/rust-lang/rfcs/blob/master/text/0505-api-comment-conventions.md) to find out whether some more advanced statements can be used. In general, writing documentation is dreaded by most developers, which is why it's very important to make it as simple and effortless as possible. The /// pattern is quite common and has been expanded in Rust so that the documentation can apply to the code that follows (///) or that contains it (//!). Examples can be seen in step 1 and step 4.
The approach the Rust project chose allows for a few lines explaining the (public) function, and then the rustdoc compiler (invoked in step 2 with cargo doc) does the rest: exposing public members, cross-linking, listing all of the available types and modules, and much more. While the output is fully customizable (step 6), the default is already visually quite appealing (we think).
Special sections (step 3) add another dimension to the documentation output: they allow for IDEs or editors to make some sense of the provided information and highlight, for example, that (and when) a function may panic. The examples section in your newly generated documentation will even compile and run code in the form of doctests (see the Testing your documentation recipe) so you will be notified when your examples become invalid.
The rustdoc output is also independent of a web server, which means that it can be used wherever static hosting is supported. In fact, the Rust project builds and serves every crate's documentation that is hosted on https://crates.io, on https://docs.rs.
Now that we can create documentation successfully, we should move on to the next recipe.