Skill v1.0.0
currentLLM-judged scan90/100version: "1.0.0" name: google-rust-style description: "Google's Rust style guide for coding agents: paste-ready rustfmt.toml, clippy.toml and [lints], the judgement rules no tool checks (module layout, imports, API shape, panics, naming), and where Google overrides common Rust advice. Use when writing or reviewing Rust to Google style, or setting up a Rust crate's lint config." license: CC-BY-4.0
Google Rust style
Source: Google Rust Style Guide, read 2026-10-22 at gh-pages 2ac1584. Section links below use G#anchor, short for https://google.github.io/styleguide/rustguide.html#anchor.
This skill holds only what Google adds on top of common Rust advice (borrow before you clone, thiserror for library errors, cargo clippy clean). Pair it with a general Rust skill for the basics. Where common advice disagrees with Google, Google wins (see Overrides).
Out of scope: rules tagged Chrome, Fuchsia or Android (small crates, flat rust_library layout, chromium::import!, no async, no std::thread, fuchsia_sync::Mutex, per-codebase test frameworks).
Library or application
First decide which kind of crate you are in (G#api-design-context):
- Library: used by other projects and solves a general problem. Every API rule here binds.
- Application: one app and one specific problem. API rules are strong recommendations, and simplicity and directness win over reuse.
Rules below marked library follow this split. Every other rule binds both kinds: an application with no outside users still takes the full rustfmt and clippy config except the library block, and every layout, import, error, naming and unsafe rule.
1. Tool config
Run cargo clippy --all-targets --all-features -- -D warnings in CI so every warn below fails the build.
rustfmt.toml. All three options need nightly rustfmt. Stable 1.98 prints "unstable features are only available in nightly" and ignores them, and the guide lets a stable-only codebase skip them. max_width = 100 is already rustfmt's default, so it is not set.
imports_granularity = "Module"hex_literal_case = "Lower"format_macro_matchers = true
clippy.toml. avoid-breaking-exported-api = false is load-bearing. Without it, wrong_self_convention and ref_option skip pub items, which are exactly the items Google's rules target.
avoid-breaking-exported-api = falseallow-unwrap-in-tests = truedisallowed-methods = [{ path = "std::panic::catch_unwind", reason = "Google Rust style forbids catch_unwind in first-party code" },{ path = "std::sync::PoisonError::into_inner", reason = "Google Rust style: never reach inside a poisoned mutex" },{ path = "std::sync::PoisonError::get_ref", reason = "Google Rust style: never reach inside a poisoned mutex" },{ path = "std::sync::PoisonError::get_mut", reason = "Google Rust style: never reach inside a poisoned mutex" },]
Cargo.toml, every crate. Drop unsafe_code = "forbid" only in the one crate that isolates unsafe code.
[lints.rust]unsafe_code = "forbid"unstable_features = "deny"[lints.clippy]ref_option = "deny"unwrap_used = "deny"wildcard_imports = "deny"needless_for_each = "deny"undocumented_unsafe_blocks = "deny"derive_partial_eq_without_eq = "allow"missing_const_for_fn = "allow"
Library crates merge these keys into the same two tables.
[lints.rust]missing_docs = "warn"[lints.clippy]exhaustive_enums = "deny"exhaustive_structs = "deny"must_use_candidate = "deny"return_self_not_must_use = "deny"missing_panics_doc = "deny"
To take a guide-sanctioned exception, write #[expect(<lint>, reason = "...")] on the item. The reason carries the why, so the item needs no comment.
| Setting | Google rule | Section | |
|---|---|---|---|
imports_granularity = "Module" | A use imports from one module; no :: inside {} | G#use-statement | |
hex_literal_case, format_macro_matchers | Lowercase hex literals; formatted macro matchers | G#formatting-other | |
wildcard_imports | No glob imports. It already exempts Google's allowed cases: use super::* in tests, use Enum::* inside a function, pub use sub::* | G#glob-imports | |
ref_option, plus default borrowed_box and ptr_arg | Take &T, &[T], &str, Option<&T>, not &Box<T>, &Vec<T>, &String, &Option<T> | G#flexible-references | |
default should_implement_trait | impl FromStr rather than an inherent from_str | G#known-traits | |
default derivable_impls | Derive when a derive can express it | G#derive | |
default new_without_default | A type with new() implements Default | G#important-traits | |
derive_partial_eq_without_eq = "allow" | Prefer PartialEq over Eq unless you mean reflexivity. The nursery lint pushes the other way | G#important-traits | |
default wrong_self_convention | as_, to_, into_ take the matching self | G#naming-methods | |
default len_without_is_empty | len() comes with is_empty() | G#expected-api-sets | |
disallowed-methods catch_unwind | catch_unwind is prohibited. The defense-in-depth entry-point wrapper takes an #[expect] | G#catch-unwind | |
disallowed-methods PoisonError | Never reach inside a poisoned mutex | G#poison | |
exhaustive_enums, exhaustive_structs | Public enums and all-pub-field structs used by other teams are #[non_exhaustive] | G#non-exhaustive-enums, G#non-exhaustive-structs | |
must_use_candidate, return_self_not_must_use | #[must_use] on pure functions and on types whose value must be consumed | G#must-use-on-functions, G#must-use-on-types | |
default unnecessary_unwrap | Pattern-match instead of check-then-unwrap | G#dont-check-then-unwrap | |
unwrap_used, allow-unwrap-in-tests | expect over unwrap; tests may panic | G#expect-over-unwrap | |
needless_for_each | A for loop over Iterator::for_each | G#combinator-chains | |
unsafe_code, undocumented_unsafe_blocks, default missing_safety_doc | Unsafe code is isolated in one crate, each block carries // SAFETY:, and unsafe fns document their invariants | G#unsafe, G#writing-doc | |
missing_docs, missing_panics_doc | Document public APIs and every panic on malformed input | G#writing-doc | |
unstable_features | No #![feature]. On stable it is already a hard error; the lint covers nightly builds | G#unstable-features | |
missing_const_for_fn = "allow" | A public fn is const only if it will stay const. The nursery lint pushes the other way | G#const-functions |
2. Judgement rules
No tool can check these. non_exhaustive_omitted_patterns is the lint the guide names for the matching rule, but it is unknown on stable 1.98 and needs a nightly feature, which would break the no-unstable-features rule. That rule stays here as prose.
Modules and imports
- A leaf module with one primary item
Fooismod fooinfoo.rs. G#userdefined-item - Non-leaf modules hold only
moddeclarations and re-exports, plus at most a little glue code. Amod testsor#[cxx::bridge]child still counts as a leaf module. G#non-leaf-modules - Define no preludes. Using a third-party prelude is the reviewer's call. G#glob-imports
- Import a trait inside the function body that uses it, unless the whole file uses it. G#prefer-local-trait-imports
- Call free functions through their module:
use std::mem;thenmem::swap(..). Calling a name directly is fine when the name is already unambiguous (C++ interop) or the module name collides. G#toplevel-functions - Name types unqualified:
use regex::Regex;thenRegex. Use the module path (io::Error) or a rename (IoError) for an ambiguous name, a large type family (rustc'shir), macros and generated code, and huge import lists. G#type-names
Formatting
#[rustfmt::skip]is for hand alignment (matrices, rows of similar lines) and generated code only. Personal taste is not a reason. G#formatting-optout- Write invisible characters in literals as escapes, such as
\u{200D}. Clippy 0.1.98 does not flag a raw zero-width joiner. G#formatting-other
Ownership and signatures
- Model ownership as unique owners plus borrows. Reach for
Rc/Arconly when that structure becomes unreasonably complex, and make the case in review. G#avoid-rc-arc - Library: return an iterator (
impl Iteratoror a concrete type) instead of aVec<T>. Return theVecwhen most callers need one, the implementation naturally builds one, the result will be indexed, the function exists to build a collection, or it hands back an existing vector. Application: aVecis fine. G#return-iterators - Library: take
impl IntoIteratorwhen the function only iterates. Application:&[T]is fine. G#accept-iterators impl Into<T>parameters belong in library APIs where call-site ergonomics are paramount. Application code takesT. G#accepting-into- When a
boolargument's meaning is unclear at the call site, make it a two-variantenum. If you keep thebool, name it in an inline comment at the call site:connect(addr, /* retry= */ true). G#enum-vs-bool - Choose the error contract:
Optionwhen the cause is obvious (checked_add, a missing value).Resultwhen the caller needs the cause to recover (I/O, network).panic!for bugs and broken preconditions, which get fixed in code rather than handled at runtime.
Developer-facing binaries and tests may panic. A codebase may choose Result even for broken invariants. G#reporting-errors
Types and traits
- Use a newtype instead of an alias when the semantics differ, the type deserves its own API, the underlying type may change, or values are easy to mix up (
Day,Month,Year). G#newtypes-vs-type-aliases - Use named fields instead of tuples and multi-field tuple structs. Newtypes and very local tuples are fine. G#prefer-named-fields
- Library code anticipates the trait impls users will need. Application code adds them on demand. G#traits-yagni
- A trait exists for a generic bound, for
impl Trait/dyn Traitin a signature, or as an extension trait. Everything else is an inherentimpl. G#trait-vs-inherent-impl - An extension trait is named
FooExtand owned by whoever ownsFoo. A foundational library may extend foreign types. An input trait (DateLike, implemented on foreign types) is not an extension trait and is fine. G#extension-traits, G#naming-extension-traits - Trait impls (G#important-traits):
- Types with public fields implement the traits all their fields implement.
- Error types are
Copywhenever no field prevents it. - Implement
Copyonly when the type will stayCopy. - Most types implement
Clone. Types that represent an external resource don't. PartialEq/PartialOrdgive users==/<. AddEq/Ordonly for reflexivity or a total order.- A likely map key or cache key implements
HashandEq, plusOrdwhen the type has a clear total order. - If a
Fromimpl is a primary way to build a type, also add an inherentnew()orfrom_foo(). G#from-impls - An all-pub-field options struct implements
Defaultso callers can writeOpts { a: 1, ..Default::default() }. Don't mark it#[non_exhaustive], which blocks that syntax outside the crate. G#options-should-implement-default, G#non-exhaustive-structs - Take an
#[expect]onexhaustive_enums/exhaustive_structsfor a fixed ontology (Color,Rectangle), a bool-like two-variant enum, or a type whose breakage you will fix yourself. G#non-exhaustive-enums - When matching a foreign
#[non_exhaustive]enum, list every known variant and fold the ones you ignore into the wildcard (Shape::Triangle | _ => {}). When you care about only one or two variants, useif letorlet else. G#non-exhaustive-enum-matching - Operator overloads keep their mathematical or logical meaning. G#operator-overloads
Macros
- Express an API with the first option that works: plain functions, types, traits and modules, then
macro_rules!, then other code generation, thenproc_macro. Count the cost: weaker IDE and search support, a local dialect, harder large-scale changes. G#macros
Naming
- Lifetimes get descriptive names, especially on types. Use
'_where it works. Single letters are fine for the input-to-output link of one method or a localfor<'a>, at most two of them. G#lifetimes - Generic parameters get short names that hint at their main bound (
R: Read). UseT/Uonly for nearly unbounded APIs such asVec<T>. G#generic-parameters - Constructors are
new(),new_with_foo()for alternatives, andtry_new()when they return aResult. Conversions arefrom_foo()backed by aFromimpl. G#naming-initialization-functions - Accessors:
foo(),foo_mut(),set_foo(v),take_foo()(resets the field and returns the old value),replace_foo(v)(returns the old value), andwith_foo(|foo| ..)/with_mut_foo(..). Use the closure form when the field sits behind aMutexorRefCell(so the guard type stays private) or a setter would need to clone a value that can't be cloned. G#getters-and-setters, G#closure-accessors - Spell words out. Keep only abbreviations an outsider knows, such as Wikipedia-common ones like
IpAddr. Don't drop letters from a word (cnt). G#avoid-abbreviations
Docs
- A doc says what the code does not. Omit one that restates the name and put
#[expect(missing_docs, reason = "...")]there instead. Self-explanatory enum variants may go undocumented. Don't explain language basics. Examples need to cover the crate somewhere, not every function. Write///and//!by hand rather than generating them from macros. G#writing-doc - Use
//for implementation notes and internal TODOs. One item can carry both:///for users,//for maintainers. G#writing-doc
Implementation
- Write unsafe code only when no safe version exists, and wrap it in a safe abstraction, ideally in its own crate with few dependencies. Write a technical justification, per block or for a whole file or build target: why it can't be safe, and the benchmark if the reason is speed. G#unsafe
- Treat
#[doc(hidden)]items you don't own as private. G#doc-hidden - Nothing runs before
main(): noctorcrate, no life-before-main tricks. G#global-initialization - Annotate enough types that a reader familiar with the codebase can tell what was inferred. Put the type on the binding rather than in a turbofish, especially after
.into(),.parse()and long iterator chains. Use::<_>where the compiler demands a turbofish. G#type-inference - Give an
assert!a message when it can say why the invariant holds, show the offending value, or tell API misuse apart from an internal bug. The message replaces a comment and shows up in logs. A message that only restates the condition adds nothing. G#assertion-context - Bind a combinator chain to a named variable before the
forthat consumes it. Rewrite long chains with branches or outside mutation as a loop. Keep short chains whose combinator carries the logic (any,sum). G#combinator-chains - Join three or more strings with
format!/write!rather than+. Fill a literal template withformat!rather thanstr::replaceor a homemade template syntax. G#prefer-format-or-write
Overrides
When common Rust advice, in another skill or in the codebase, says otherwise, follow Google:
| Topic | Common advice | ||
|---|---|---|---|
| Import granularity | imports_granularity = "Crate" | "Module" (G#use-statement). group_imports and reorder_imports can stay | |
unwrap/expect, panics | No unwrap() or expect() outside tests; avoid panic! in production | panic! for bugs and broken invariants, with expect naming the invariant (G#reporting-errors, G#expect-over-unwrap). Enable unwrap_used, not expect_used | |
| Loops vs combinators | Prefer iterator chains over for loops, including chains in a for header | for when clearer, never a chain in the header, avoid for_each (G#combinator-chains) | |
impl Into<String> params | Use them in constructors and builders everywhere | Library-only, and only when ergonomics are paramount (G#accepting-into) | |
| Shared state | Arc<Mutex<T>> / Rc<RefCell<T>> for shared mutable state | Unique ownership first; Rc/Arc only when the alternative is unreasonably complex (G#avoid-rc-arc) | |
| Traits | Design trait hierarchies; add extension traits to any type, such as StringExt for str | A trait only for generics, impl/dyn, or extension; extension traits are owned by the type's owner (G#trait-vs-inherent-impl, G#extension-traits) | |
| Error types | thiserror enums carrying String payloads | Copy whenever possible (G#important-traits). Give a variant a payload only when the caller needs it | |
clippy::nursery | Enable -W clippy::nursery | Allow derive_partial_eq_without_eq and missing_const_for_fn, which push against G#important-traits and G#const-functions | |
missing_docs level | #![deny(missing_docs)] for libraries | warn plus #[expect] on trivial items (G#writing-doc) |