Personal Rust Style Guide
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 usefoo.rsandfoo/bar.rs. -
Order of annotations on items:
- Doc comments.
- Attributes (built-ins,
cfg, and attribute macros). derivemacros.
/// 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)modulespub(crate)modulespubmodules
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
implblocks- 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.