π¦ Google Rust Style Guide: Key Points
Tip: TL;DR: Readability > writeability. Follow
rustfmt+ public Rust conventions. Avoid magic (macros, globs,Rc,unsafe,unwrap). Be explicit about ownership, errors, and types.
π§ Step 0: Know the Ground Rulesβ
Goals behind every rule
| Goal | Meaning |
| π Readability first | Code lives long; spend a bit more effort writing it |
| π€ Automation-friendly | Consistent code makes large-scale refactors (LSCs) easy |
| π Match the ecosystem | Follow the official Rust Style Guide + Rust API Guidelines unless this guide conflicts |
| βοΈ Rules must pull their weight | Only rules with a real benefit |
| π End bikeshedding | Settle common disagreements once |
Library vs Application code
| Library code | Application code | |
| Used by | Other projects | One app |
| API design rules | Binding | Strong recommendation |
| Mindset | Flexible, general, reusable | Simple, direct, no premature abstraction |
π Step 1: Organize Your Codeβ
-
Prefer small crates (Chromium): ideally one file, one abstraction. Split before adding submodules.
-
Flat file layout (Chromium): one crate's
.rsfiles live in one directory. Needing subdirectories = the crate is too big. -
Name files after the primary item:
struct Fooβfoo.rs/mod foo. -
Non-leaf modules hold only
moddeclarations and re-exports, no real implementation (a little glue code is OK). A leaf module may still containmod testand binding modules like#[cxx::bridge]. -
The root module may re-export public APIs from leaf submodules, so submodules stay an implementation detail.
-
Decide first whether APIs belong in the same crate; only then consider submodules.
-
Larger crates are OK when trait impl coherence rules force types and traits together, or for large foundational libraries.
-
Exception:
//third_partycode also built with Cargo keeps the normal Cargo directory layout.
π¦ Step 2: Write Imports Correctlyβ
β No glob importsβ
Allowed exceptions only:
-
use super::*in inline modules (e.g.mod test) -
use MyEnum::*inside functions mostly dealing with that enum -
pub use submodule::*in non-leaf modules -
Merging C++ namespaces into one module (the merged module must not be public)
-
Third-party preludes (reviewer's call). Don't define your own preludes.
β
One module per use statementβ
Matches rustfmt setting imports_granularity = Module (no :: inside {}):
// β
Good
use foo::Foo;
use foo::bar::{Bar, Baz};
// β Bad
use foo::{Foo, bar::Bar};
β Import traits locallyβ
Unless the trait's methods are used extensively throughout the file.
fn foo(s: &str) -> Result<u8, std::num::ParseIntError> {
use std::str::FromStr; // scoped to where it's used
u8::from_str(s)
}
β Functions: qualify with module nameβ
// β
Good
use std::mem;
let x = mem::transmute(y);
// β Bad
use std::mem::transmute;
let x = transmute(y);
Exceptions: well-disambiguated names (e.g. C++ free functions), small single-purpose first-party crates (Chromium), and module names prone to collisions (e.g. C++ namespace names).
β Types: import and use unqualifiedβ
Write Regex, not regex::Regex. Exceptions: ambiguous names (io::Error or use std::io::Error as IoError), large type groups (e.g. rustc's hir), macros/generated code, and code with so many dependencies that the import block would become huge.
Chromium onlyβ
Use chromium::import!() as the import mechanism.
π¨ Step 3: Formattingβ
-
Run the formatter (
rustfmt/git cl format). Don't argue about formatting manually. -
Recommended
rustfmtoptions (a codebase may skip any that aren't available on stable Rust):
max_width = 100
hex_literal_case = "Lower" # 0xab12, not 0xAB12
format_macro_matchers = true
imports_granularity = "Module"
-
Escape invisible characters in literals:
\u{200D}. -
#[rustfmt::skip]only for intentional manual alignment (e.g. matrices) or generated code. Never for personal taste.
π Step 4: Ownership & Borrowingβ
| Rule | Detail |
Avoid Rc / Arc | Prefer unique ownership + borrowing. Use them only when the alternative is unreasonably complex. |
Prefer borrows over .clone() | Clone only when you truly need a deep copy. Cloning Rc/Arc is fine (cheap pointer copy). |
π Step 5: Function Inputs & Outputsβ
Accept the most flexible referenceβ
| β Use | β Instead of |
&T | &Box<T> |
&[T] | &Vec<T> |
&str | &String |
Option<&T> | &Option<T> |
Exception: non-linear character data such as Rope/Cord. Related Clippy lints: borrowed_box, ptr_arg.
Iterators vs collectionsβ
| Situation | Library | Application |
| Return values | Prefer impl Iterator | Vec<T> is fine if simpler |
| Parameters | Accept impl IntoIterator | &[T] is fine if simpler |
Return Vec anyway when: most callers need a Vec, the implementation naturally builds one, the result needs random indexing, a for loop fits better and the allocation doesn't matter, the function's purpose is to create a collection, or it returns an existing vector.
Other input rulesβ
-
impl Into<T>params: libraries only, when call-site ergonomics really matter. Weigh it against code bloat, complex signatures, and inference trouble. Application code: almost never. -
**Prefer
enumover **boolwhen the meaning isn't obvious. If you keep abool, comment it at the call site:
connect(addr, /* retry= */ true);
π¨ Step 6: Report Errors Properlyβ
| Mechanism | When | Example |
Option | Obvious, expected failure; no detail needed | checked_add() -> Option<u32> |
Result | Recoverable, external failures; caller needs to know why | open_file() -> Result<File, io::Error> |
panic! | Bugs / violated invariants: fix the code, don't handle at runtime | Index out of bounds |
Coarse-grained recovery from a panic may be possible with catch_unwind (see Step 14 limits). Exceptions: dev-only binaries and tests may panic. Some codebases ban panics entirely and use Result even for broken invariants.
π§± Step 7: Typesβ
Newtype vs type alias. Use a newtype when:
-
Semantics differ from the underlying type
-
The type needs its own API
-
You may change the underlying type later
-
Types are easy to mix up (e.g.
Day,Month,Year)
Named fields over tuples:
// β
Good
struct Point { x: f64, y: f64 }
// β Avoid (except one-field newtypes or very local use)
let p = (1.0, 2.0); p.0; p.1;
π§© Step 8: Traitsβ
When to define a traitβ
Only for:
-
Generic bounds (
T: Trait) -
impl Trait/dyn Trait -
Extension traits Otherwise β inherent ****
impl. Don't use traits to organize code.
General trait rulesβ
-
Library code: add impls proactively. Application code: add them when needed.
-
Implement std traits instead of custom methods (
impl FromStr, notfn from_str). Keeping both the method and the trait impl is fine for discoverability (Clippy:should_implement_trait). -
Use
#[derive(...)]whenever possible. -
Libraries: prefer generics over
dyn Trait(no fat pointer or vtable lookup, inlinable), unless code size matters. Note: some traits aren'tdyncompatible (generic methods, associated functions withoutself, methods returningSelf).
Extension traitsβ
-
Suffix with
ExtβForeignTypeExt -
The team owning the type should own the extension (avoids method-name clashes).
-
Exception: widely useful extensions in foundational libraries/frameworks.
-
Not to be confused with input traits (e.g.
DateLikeimplemented on many foreign types), which are fine. -
Can also be blanket-implemented:
impl<T: ForeignTrait> ForeignTraitExt for T.
Trait cheat sheetβ
| Trait | Guidance |
Copy | Implement when it will clearly stay Copy. Error types: Copy whenever possible. |
Clone | Most types, unless they represent an external resource |
Default | If there's a new() or a default state |
PartialEq / PartialOrd | Prefer these for == / <. Only add Eq / Ord when you guarantee reflexivity / total order. |
Hash + Eq | If likely used as a map key or cache key; add Ord too if there's a clear total order |
| Types with public fields | Consider implementing the traits all subfields implement |
From | If it's a main constructor, also add new() / from_foo() |
| Options structs | Implement Default β enables ..Default::default() |
π§΅ Step 9: Concurrencyβ
-
Never reach into a poisoned mutex.
-
Chromium: no
async/.await, nostd::thread(use sequences). -
Fuchsia: use
fuchsia_sync::Mutex.
πͺ Step 10: Macros (Last Resort)β
Preference order:
-
Plain functions, types, traits, modules β
-
macro_rules! -
Other codegen (e.g. genrule)
-
proc_macroβ οΈ Why: macros hurt IDE support, create mini-dialects, and make LSCs harder. If you need codegen,macro_rules!works on the AST, so it's less fragile than textual generation.
π·οΈ Step 11: Namingβ
| Item | Convention |
| Conversions | as_..() (cheap borrow), to_..() (expensive), into_..() (consumes self) |
| Constructors | new(), new_with_foo(), try_new() if it returns Result |
| Value conversions | from_foo() + matching From impl |
| Extension traits | FooExt |
| Lifetimes | Descriptive names; 'a OK for simple inputβoutput links or local for<'a> bounds (max 2); '_ fine; generic abstractions like Cow exempt |
| Generics | Short, meaningful (from main trait bound); T/U for unbounded generics |
| Abbreviations | Avoid unless common (on Wikipedia) or clear outside your project (e.g. IpAddr). Never drop letters inside words. |
Getters & settersβ
| Method | Purpose |
foo() | Returns &T (or T if Copy). No get_ prefix. |
foo_mut() | Returns &mut T |
set_foo(v) | Sets value |
take_foo() | Clears to default/None, returns old |
replace_foo(v) | Sets value, returns old |
with_foo(|f| ..) / with_mut_foo(|f| ..) | Closure access, great for Mutex/RefCell (don't leak guards) or when a change needs follow-up validation. The closure's return value is passed back. If only one accessor exists, with_foo may also mutate. |
π Step 12: Documentationβ
-
Document all public APIs (use the
missing_docslint). -
///for items,//!for module/crate (top of file),//for implementation notes and TODOs. Mixing them is encouraged. -
Use Markdown, capitalization, and punctuation.
-
Always document panic cases and
unsafe** invariants**. -
Include examples somewhere in the crate (not needed on every function).
-
Don't explain language basics (e.g. "a mock implementation of the trait, for tests" on a
MockFileSystemimpl). -
Docs on self-explanatory enum variants can be omitted.
-
Write docs directly with
//////!, not generated from macros. -
Don't restate the obvious:
// β Pointless (better to omit and allow the lint)
/// Removes an element.
pub fn remove_element() -> Element {}
// β
Useful: public doc + private note
/// Finds the first `FooItem` with the given `name`, or `None` if not present.
// TODO: crbug.com/12345 - `name` should be &str, not String.
pub fn get_named(items: &[FooItem], name: String) -> Option<&FooItem> { .. }
βοΈ Step 13: Other Language Featuresβ
| Topic | Rule |
unsafe | Only if no safe alternative. Isolate in a safe abstraction, add a // SAFETY: comment, justify it (with benchmarks if for performance). |
| Unstable features | Don't use. Exceptions: Android embedded needs (reviewed), Chrome blanket-approved list, Fuchsia's own guidance |
len() | Also implement is_empty() |
#[doc(hidden)] | Treat as private if you don't own it |
const fn | Only if you'll keep it const (removing it is a breaking change) |
| Operator overloading | Only for real math/logic meaning, not syntax sugar |
| Public enums (used by other teams) | #[non_exhaustive] unless the set is fixed (e.g. Color, two-state enums) or you commit to fixing breakages via LSC. Rarely used enums may skip it. |
| Public structs with pub fields | #[non_exhaustive] unless fields won't change, you commit to LSC fixes, or you want ..Default::default() |
#[must_use] | On pure functions (e.g. HashMap::get), functions returning critical info, and error / lazy types. Not needed if the return type is already #[must_use] (like Result). |
Code before main() | Never (e.g. no ctor crate) |
π οΈ Step 14: Implementation Habitsβ
Add explicit types where inference hides thingsβ
Especially after .into(), .parse(), and complex iterator chains. Prefer annotating the variable over turbofish:
let port: u16 = s.parse()?;
If the compiler forces a turbofish, a _ marker is fine: collect::<Vec<_>>().
Pattern match instead of check-then-unwrapβ
// β
Good
if let Some(value) = option {
use_value(value);
}
// β Bad
if option.is_some() {
use_value(option.unwrap());
}
expect over unwrapβ
Document why the invariant holds: .expect("config loaded at startup").
Matching #[non_exhaustive] enumsβ
List known variants explicitly, then _:
match shape {
Shape::Circle => println!("circle"),
Shape::Square => println!("square"),
Shape::Triangle | _ => {}
}
// β
Only care about one variant β if let
if let Shape::Circle = shape { println!("circle"); }
// β Hides variants behind _
match shape {
Shape::Circle => println!("circle"),
_ => {}
}
Exception: match is fine when you only care about some variants and it's cleaner than if let.
Give assertions contextβ
// β
Explains why
assert!(values.is_sorted(), "`values` comes from a PriorityQueue, so it should be sorted");
assert!(refund > 0, "Refund must be positive, got {refund:?}");
assert!(self.stream.is_some(), "Call connect() before executing queries");
// β Just repeats the condition
assert!(values.is_sorted(), "values should always be sorted");
Iterator chains vs for loopsβ
-
β Short, logic-heavy combinators:
values.iter().any(|&v| v > threshold) -
β Long chains with conditionals or external mutation β use a
forloop -
β Chains in a
forheader β assign to a named variable first -
β
for_eachβ usually aforloop reads better
let urgent_bugs = bug_descriptions
.iter()
.filter_map(|d| parse_bug_description(d).ok())
.filter(|bug| bug.priority <= Priority::P1);
for bug in urgent_bugs {
assign_bug(bug);
}
Other habitsβ
-
No ****
panic::catch_unwind. Only allowed as defense-in-depth that nothing relies on, and only where code may run withpanic=unwind(Chrome, Android, and Fuchsia usepanic=abort). -
Strings:
format!("{a}{b}{c}")instead ofa + b + c(fewer allocations) orstr::replacetemplating. Don't invent your own template syntax. -
Tests: use the codebase's framework (Chrome:
rust_gtest_interop, Fuchsia:#[fuchsia::test]).
β Quick Review Checklistβ
-
rustfmtrun, no glob imports, one module peruse -
Functions called as
module::func(), types used unqualified -
No unnecessary
Rc/Arc/.clone() -
Params use
&str,&[T],&T,Option<&T> -
enuminstead of unclearboolparams -
Right error type:
Option/Result/panic! -
Named fields over tuples, newtypes where meaning differs
-
Traits only for generics / dyn / extensions;
#[derive]used -
Getters without
get_, constructorsnew/try_new -
Public API documented, panics and
unsafeexplained -
if let/matchinstead ofunwrap;expectwith a reason -
Assertions carry context messages
-
Readable iterator chains,
format!for string building -
No
unsafe, unstable features, or macros without strong justification