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.rs
andfoo/bar.rs
. -
Order of annotations on items:
- Doc comments.
- Attributes (built-ins,
cfg
, and attribute macros). 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)
modulespub(crate)
modulespub
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.