rustfmt ensures that Rust projects have a consistent style, but leaves open some style-like questions like how to order content within a Rust file. This guide represents my personal preferences that I add on to rustfmt for the sake of consistency.

When I discover cases where I need to break these rules, I'll either:

  • Remove the offending rule, because it clearly isn't actually a rule.
  • Update the offending rule to be more general so that it isn't violated.

Preliminaries

  • Every item (type, method, function) gets a doc comment (even private items), even if it's a one-liner. This means enabling

    #![deny(missing_docs)]
    #![deny(clippy::missing_docs_in_private_items)]
    

    at the crate-level. These lints can be enabled at a per-module level if migrating an existing project to this style guide.

  • Never use foo/mod.rs. Always use foo.rs and foo/bar.rs.

  • Order of annotations on items:

    1. Doc comments.
    2. Attributes (built-ins, cfg, and attribute macros).
    3. derive macros.
    /// The doc-comment.
    #[must_use]
    #[derive(Debug, Deserialize, Serialize)]
    enum Foo {
        /// The comment.
        #[serde(skip)]
        Bar,
        /// The comment.
        Baz,
    }
    

Module prelude

These things go at the top of module, in this order.

Module doc-comment

//! This goes first.

Use //! for modules which exist in separate files, and /// for inline modules.

Module Attributes

Any attributes which should apply to the whole module should go after the module doc-comment. Module attibutes always go inside the module rather than outside the module with an item attribute.

Do:

// In `foo.rs`

//! The module.

#![cfg(target_os = "linux")]

Do:

/// The module.
#![cfg(target_os = "linux")]
mod foo {}

Don't:

#[cfg(target_os = "linux")]
mod foo;

Imports

Imports are structured in 3 alphabetical groups.

  • Imports from the standard library.
  • Imports from external crates.
  • Imports from the current crate.

This corresponds to the unstable rustfmt feature group_imports.

use std::{
    fs,
    io::{Read, Write},
    thread::{self, Thread},
};

use futures::{FutureExt, StreamExt};
use serde::{Deserialize, Serialize};

use self::foo::Bar;
use crate::web::Error;

Note: crate imports and self imports are specified separately. This makes clearer the separation between items imported from modules declared in the current scope (which are more likely to be pub(super)) and items imported from sibling modules in the module hierarchy (which are more likely to be pub(crate)).

Submodule declarations and re-exports

Submodule declarations should be grouped by visibility, in order of increasing visibility. This means the order should be

  • Private modules
  • pub(super) modules
  • pub(crate) modules
  • pub modules

Modules which exist in separate files and modules which are defined inline with mod {} can be freely mixed within a group. Modules should be alphabetized within a group.

Each group of modules is optionally followed by a block of re-exports with the same visibility as that group of modules.

Ex.

pub(crate) mod foo;
/// Bar doc comment.
pub(crate) mod bar {
    /// The function doc comment.
    pub(crate) fn baz() {}
}
pub(crate) use self::{bar::baz, foo::*};

Type aliases

Type aliases for the module, grouped by visibility (same as modules).

Module contents

Try to split modules into three kinds:

  • Modules that declare types, their methods, and their trait implementations.
  • Modules that declare traits and their implementations on external types.
  • Modules that declare free functions and macros.

But these are general classes, not hard rules. The order of items within a module is the same regardless of the kind of module it is. The order of items within a module should be

  • decl macros
  • traits
  • structs and enums
  • impl blocks
  • trait implementations
  • functions

All items should be alphabetical. impl blocks of the same type and trait implementations for the same trait should be ordered from most to least generic.