Writing Rust Documentation
Tags: rust, developer, beginners, tutorial
Published: 2024-06-12 13:49:30

Writing effective documentation is crucial for any programming language, and Rust is no exception. Good documentation helps users understand how to use your code, what it does, and why it matters. In this guide, I'll walk you through the best practices for writing documentation in Rust, ensuring that your code is not only functional but also accessible and user-friendly.

## Introduction to Rust Documentation

Rust documentation is typically written using `rustdoc`, a tool that generates HTML documentation from comments in your source code. The primary goal of `rustdoc` is to make it easy for developers like you and me to document our code in a way that is both comprehensive and easy to understand.

### Why Documentation Matters

Documentation serves several key purposes:

1. **Communication**: It explains what your code does, how it works, and how to use it.
2. **Maintenance**: It helps future developers (including yourself) understand the codebase, making it easier to maintain and extend.
3. **Onboarding**: It aids new team members in getting up to speed with the project.
4. **Community**: It allows the broader community to use and contribute to your project.

## Writing Documentation in Rust

### Commenting Your Code

Rust uses three types of comments for documentation:

1. **Line comments**: Start with `//` and are used for short, single-line comments.
2. **Block comments**: Enclosed in `/* ... */` and can span multiple lines.
3. **Doc comments**: Start with `///` for item-level documentation or `//!` for module-level documentation.

#### Item-Level Documentation

Item-level documentation comments (`///`) are used to describe functions, structs, enums, traits, and other items. These comments should be placed directly above the item they describe.

{% embed https://gist.github.com/gritmaxuk/3c215427b7b1f552ae7fa9b1b9de7607 %}

In this example, the `add` function is documented with a brief description and an example of how to use it. The `# Examples` section is a common convention in Rust documentation, providing a clear and concise way to demonstrate usage.

#### Module-Level Documentation

Module-level documentation comments (`//!`) are used to describe the overall purpose and functionality of a module. These comments should be placed at the top of the module file.

{% embed https://gist.github.com/gritmaxuk/5b085c4cab08c9d83fecfcfaf40ba043 %}

Here, the module-level comment provides a high-level overview of the module's purpose, while the item-level comments describe the individual functions.

### Structuring Your Documentation

Good documentation is well-structured and easy to navigate. Here are some tips for structuring your Rust documentation:

1. **Use Sections**: Break your documentation into sections using headings. Common sections include `# Examples`, `# Panics`, `# Errors`, and `# Safety`.
2. **Provide Examples**: Examples are one of the most effective ways to demonstrate how to use your code. Include them wherever possible.
3. **Explain Edge Cases**: Document any edge cases or special conditions that users need to be aware of.
4. **Link to Related Items**: Use intra-doc links to connect related items within your documentation. This helps users navigate your documentation more easily.

#### Using Sections

Sections help organize your documentation and make it easier to read. Here are some common sections you might include:

- **Examples**: Show how to use the item.
- **Panics**: Describe any conditions under which the item might panic.
- **Errors**: Explain any errors that might be returned.
- **Safety**: For `unsafe` code, explain why it is safe to use.

{% embed https://gist.github.com/gritmaxuk/4a18ead577fb92ed41273f954e3b344e %}

In this example, the `divide` function documentation includes an `# Examples` section to show how to use the function and a `# Panics` section to describe a condition that will cause the function to panic.

#### Providing Examples

Examples are crucial for helping users understand how to use your code. They should be simple, clear, and demonstrate the most common use cases.

{% embed https://gist.github.com/gritmaxuk/3c829d1d5f5715ca6564511cca6f546c %}

This example shows how to calculate the factorial of a number, providing a clear and concise example of how to use the `factorial` function.

#### Explaining Edge Cases

Edge cases are situations that occur outside of normal operating conditions. Documenting these cases helps users understand how your code behaves in less common scenarios.

{% embed https://gist.github.com/gritmaxuk/0312b7a4fe4a26c62a9e35ff1de5286b %}

In this example, the `fibonacci` function documentation includes a `# Panics` section to explain that the function will panic if the input is too large.

#### Linking to Related Items

Intra-doc links allow you to create hyperlinks to other items within your documentation. This helps users navigate your documentation more easily.

{% embed https://gist.github.com/gritmaxuk/49aa865a3ffcc584a0d601302001b4c8 %}

In this example, the `add` and `subtract` functions link to each other using intra-doc links, making it easy for users to navigate between related items.

### Using the `#[doc]` Attribute

The `#[doc]` attribute in Rust provides additional flexibility for writing documentation. You can use it to add documentation to items in a more programmatic way. This attribute is particularly useful when you need to generate documentation dynamically or when you want to include documentation that isn't directly tied to a specific item.

#### Basic Usage

The `#[doc]` attribute can be used to add documentation to any item. Here's a simple example:

```rust
#[doc = "Adds two numbers together."] 
fn add(a: i32, b: i32) -> i32 { a + b }
```

This is equivalent to using the `///` comment style but allows for more complex scenarios.

#### Including External Files

You can use the `#[doc]` attribute to include documentation from external files. This is useful if you have large blocks of documentation that you want to keep separate from your code.

```rust
#[doc = include_str!("docs/add_function.md")] 
fn add(a: i32, b: i32) -> i32 { a + b }
```

In this example, the documentation for the `add` function is included from an external Markdown file.

#### Conditional Documentation

The `#[doc]` attribute can also be used to conditionally include documentation based on compile-time conditions. This is useful for documenting platform-specific behavior or features.

```rust
#[cfg(target_os = "windows")] 
#[doc = "This function is only available on Windows."] 
fn windows_only_function() { // Windows-specific code } 
 
#[cfg(target_os = "linux")] 
#[doc = "This function is only available on Linux."] 
fn linux_only_function() { // Linux-specific code }
```

In this example, different documentation is included based on the target operating system.

### Best Practices for Writing Documentation

Here are some best practices to keep in mind when writing Rust documentation:

1. **Be Clear and Concise**: Write in a clear and concise manner. Avoid unnecessary jargon and keep sentences short and to the point.
2. **Use Proper Grammar and Spelling**: Ensure your documentation is free of grammatical and spelling errors. This helps maintain a professional appearance.
3. **Be Consistent**: Use consistent terminology and formatting throughout your documentation. This makes it easier for users to understand and follow.
4. **Keep It Up to Date**: Regularly update your documentation to reflect changes in your code. Outdated documentation can be more harmful than no documentation at all.
5. **Use Code Examples**: Include code examples wherever possible. They are one of the most effective ways to demonstrate how to use your code.
6. **Document All Public Items**: Ensure that all public items (functions, structs, enums, etc.) are documented. This helps users understand how to use your library or application.

### Example of Comprehensive Documentation

Let's put it all together with a comprehensive example:

{% embed https://gist.github.com/gritmaxuk/9bc75ce310a1802c5f2641d6c742d849 %}

In this example, the module-level comment provides an overview of the module, and each function is documented with a description, examples, and any relevant panics.

## Documentation Tests

One of the powerful features of Rust's documentation system is the ability to include tests directly within your documentation comments. These are known as documentation tests, and they serve a dual purpose: they provide examples of how to use your code and ensure that the examples remain correct as your code evolves.

### Writing Documentation Tests

To write a documentation test, you simply include code examples within triple backticks in your doc comments. Rust's `rustdoc` tool will automatically extract these examples and run them as tests when you run `cargo test`.

Here's a basic example:

{% embed https://gist.github.com/gritmaxuk/5ec9a9a7bb8d4fd08d8fd08db68974fa %}

In this example, the code within the triple backticks is a documentation test. When you run `cargo test`, this code will be compiled and executed to ensure that it produces the expected result.

### Benefits of Documentation Tests

1. **Ensuring Accuracy**: Documentation tests ensure that your examples are always accurate and up-to-date. If you change your code in a way that breaks an example, the test will fail, alerting you to the issue.
2. **Providing Working Examples**: Users can trust that the examples in your documentation actually work, as they are tested alongside your code.
3. **Reducing Maintenance**: By embedding tests in your documentation, you reduce the need for separate example code files, making it easier to maintain consistency between your code and its documentation.

### Advanced Usage

You can also include multiple examples and more complex scenarios in your documentation tests. Additionally, you can use attributes to control the behavior of these tests.

For instance, you might want to ignore certain tests or only run them under specific conditions:

{% embed https://gist.github.com/gritmaxuk/30b1637f22aa55eadc8df7c674a296fc %}

In this example, the second test is ignored because it demonstrates a panic scenario. You can use the `ignore` attribute to prevent certain tests from running by default.

### Running Documentation Tests

To run your documentation tests, simply use the `cargo test` command. This will compile and run all tests, including those embedded in your documentation comments.

```sh
cargo test
```

By incorporating documentation tests into your workflow, you can ensure that your documentation remains accurate and useful, providing real value to users of your code.

## Conclusion

Writing good documentation is an essential skill for any Rust developer. By following the best practices outlined in this guide, you can create documentation that is clear, concise, and helpful to your users. Remember to use `rustdoc` to generate your documentation, and keep it up to date as your code evolves. With well-written documentation, you can make your code more accessible, maintainable, and enjoyable to use.

## Sources 

- [Rust Book](https://doc.rust-lang.org/rustdoc/how-to-write-documentation.html)