Skip to main content
Current10d ago

πŸ¦€ 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

GoalMeaning
πŸ“– Readability firstCode lives long; spend a bit more effort writing it
πŸ€– Automation-friendlyConsistent code makes large-scale refactors (LSCs) easy
🌍 Match the ecosystemFollow the official Rust Style Guide + Rust API Guidelines unless this guide conflicts
βš–οΈ Rules must pull their weightOnly rules with a real benefit
πŸ›‘ End bikesheddingSettle common disagreements once

Library vs Application code

Library codeApplication code
Used byOther projectsOne app
API design rulesBindingStrong recommendation
MindsetFlexible, general, reusableSimple, 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 .rs files 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 mod declarations and re-exports, no real implementation (a little glue code is OK). A leaf module may still contain mod test and 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_party code 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 rustfmt options (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​

RuleDetail
Avoid Rc / ArcPrefer 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​

SituationLibraryApplication
Return valuesPrefer impl IteratorVec<T> is fine if simpler
ParametersAccept 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 enum over **bool when the meaning isn't obvious. If you keep a bool, comment it at the call site:

connect(addr, /* retry= */ true);


🚨 Step 6: Report Errors Properly​

MechanismWhenExample
OptionObvious, expected failure; no detail neededchecked_add() -> Option<u32>
ResultRecoverable, external failures; caller needs to know whyopen_file() -> Result<File, io::Error>
panic!Bugs / violated invariants: fix the code, don't handle at runtimeIndex 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:

  1. Generic bounds (T: Trait)

  2. impl Trait / dyn Trait

  3. 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, not fn 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't dyn compatible (generic methods, associated functions without self, methods returning Self).

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. DateLike implemented on many foreign types), which are fine.

  • Can also be blanket-implemented: impl<T: ForeignTrait> ForeignTraitExt for T.

Trait cheat sheet​

TraitGuidance
CopyImplement when it will clearly stay Copy. Error types: Copy whenever possible.
CloneMost types, unless they represent an external resource
DefaultIf there's a new() or a default state
PartialEq / PartialOrdPrefer these for == / <. Only add Eq / Ord when you guarantee reflexivity / total order.
Hash + EqIf likely used as a map key or cache key; add Ord too if there's a clear total order
Types with public fieldsConsider implementing the traits all subfields implement
FromIf it's a main constructor, also add new() / from_foo()
Options structsImplement Default β†’ enables ..Default::default()

🧡 Step 9: Concurrency​

  • Never reach into a poisoned mutex.

  • Chromium: no async/.await, no std::thread (use sequences).

  • Fuchsia: use fuchsia_sync::Mutex.


πŸͺ„ Step 10: Macros (Last Resort)​

Preference order:

  1. Plain functions, types, traits, modules βœ…

  2. macro_rules!

  3. Other codegen (e.g. genrule)

  4. 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​

ItemConvention
Conversionsas_..() (cheap borrow), to_..() (expensive), into_..() (consumes self)
Constructorsnew(), new_with_foo(), try_new() if it returns Result
Value conversionsfrom_foo() + matching From impl
Extension traitsFooExt
LifetimesDescriptive names; 'a OK for simple input→output links or local for<'a> bounds (max 2); '_ fine; generic abstractions like Cow exempt
GenericsShort, meaningful (from main trait bound); T/U for unbounded generics
AbbreviationsAvoid unless common (on Wikipedia) or clear outside your project (e.g. IpAddr). Never drop letters inside words.

Getters & setters​

MethodPurpose
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_docs lint).

  • /// 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 MockFileSystem impl).

  • 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​

TopicRule
unsafeOnly if no safe alternative. Isolate in a safe abstraction, add a // SAFETY: comment, justify it (with benchmarks if for performance).
Unstable featuresDon'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 fnOnly if you'll keep it const (removing it is a breaking change)
Operator overloadingOnly 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 for loop

  • ❌ Chains in a for header β†’ assign to a named variable first

  • ❌ for_each β†’ usually a for loop 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 with panic=unwind (Chrome, Android, and Fuchsia use panic=abort).

  • Strings: format!("{a}{b}{c}") instead of a + b + c (fewer allocations) or str::replace templating. Don't invent your own template syntax.

  • Tests: use the codebase's framework (Chrome: rust_gtest_interop, Fuchsia: #[fuchsia::test]).


βœ… Quick Review Checklist​

  • rustfmt run, no glob imports, one module per use

  • Functions called as module::func(), types used unqualified

  • No unnecessary Rc/Arc/.clone()

  • Params use &str, &[T], &T, Option<&T>

  • enum instead of unclear bool params

  • 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_, constructors new / try_new

  • Public API documented, panics and unsafe explained

  • if let / match instead of unwrap; expect with a reason

  • Assertions carry context messages

  • Readable iterator chains, format! for string building

  • No unsafe, unstable features, or macros without strong justification

Related Articles