<< All versions

Skill v1.0.0

currentLLM-judged scan90/100
0xmassi/google-rust-style-skill/google-rust-style
──Details
PublishedOctober 22, 2026 at 02:36 PM
Content Hashsha256:4ab10879b912b15c...
Git SHA5d5eeca939d7
──Files
Files (1 file, 22.3 KB)
SKILL.md22.3 KBactive
SKILL.md · 193 lines · 22.3 KB

version: "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.

toml
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.

toml
avoid-breaking-exported-api = false
allow-unwrap-in-tests = true
disallowed-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.

toml
[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.

toml
[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.

SettingGoogle ruleSection
imports_granularity = "Module"A use imports from one module; no :: inside {}G#use-statement
hex_literal_case, format_macro_matchersLowercase hex literals; formatted macro matchersG#formatting-other
wildcard_importsNo 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_argTake &T, &[T], &str, Option<&T>, not &Box<T>, &Vec<T>, &String, &Option<T>G#flexible-references
default should_implement_traitimpl FromStr rather than an inherent from_strG#known-traits
default derivable_implsDerive when a derive can express itG#derive
default new_without_defaultA type with new() implements DefaultG#important-traits
derive_partial_eq_without_eq = "allow"Prefer PartialEq over Eq unless you mean reflexivity. The nursery lint pushes the other wayG#important-traits
default wrong_self_conventionas_, to_, into_ take the matching selfG#naming-methods
default len_without_is_emptylen() comes with is_empty()G#expected-api-sets
disallowed-methods catch_unwindcatch_unwind is prohibited. The defense-in-depth entry-point wrapper takes an #[expect]G#catch-unwind
disallowed-methods PoisonErrorNever reach inside a poisoned mutexG#poison
exhaustive_enums, exhaustive_structsPublic 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 consumedG#must-use-on-functions, G#must-use-on-types
default unnecessary_unwrapPattern-match instead of check-then-unwrapG#dont-check-then-unwrap
unwrap_used, allow-unwrap-in-testsexpect over unwrap; tests may panicG#expect-over-unwrap
needless_for_eachA for loop over Iterator::for_eachG#combinator-chains
unsafe_code, undocumented_unsafe_blocks, default missing_safety_docUnsafe code is isolated in one crate, each block carries // SAFETY:, and unsafe fns document their invariantsG#unsafe, G#writing-doc
missing_docs, missing_panics_docDocument public APIs and every panic on malformed inputG#writing-doc
unstable_featuresNo #![feature]. On stable it is already a hard error; the lint covers nightly buildsG#unstable-features
missing_const_for_fn = "allow"A public fn is const only if it will stay const. The nursery lint pushes the other wayG#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 Foo is mod foo in foo.rs. G#userdefined-item
  • Non-leaf modules hold only mod declarations and re-exports, plus at most a little glue code. A mod tests or #[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; then mem::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; then Regex. Use the module path (io::Error) or a rename (IoError) for an ambiguous name, a large type family (rustc's hir), 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/Arc only when that structure becomes unreasonably complex, and make the case in review. G#avoid-rc-arc
  • Library: return an iterator (impl Iterator or a concrete type) instead of a Vec<T>. Return the Vec when 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: a Vec is fine. G#return-iterators
  • Library: take impl IntoIterator when 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 takes T. G#accepting-into
  • When a bool argument's meaning is unclear at the call site, make it a two-variant enum. If you keep the bool, name it in an inline comment at the call site: connect(addr, /* retry= */ true). G#enum-vs-bool
  • Choose the error contract:
  • Option when the cause is obvious (checked_add, a missing value).
  • Result when 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 Trait in a signature, or as an extension trait. Everything else is an inherent impl. G#trait-vs-inherent-impl
  • An extension trait is named FooExt and owned by whoever owns Foo. 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 Copy whenever no field prevents it.
  • Implement Copy only when the type will stay Copy.
  • Most types implement Clone. Types that represent an external resource don't.
  • PartialEq/PartialOrd give users ==/<. Add Eq/Ord only for reflexivity or a total order.
  • A likely map key or cache key implements Hash and Eq, plus Ord when the type has a clear total order.
  • If a From impl is a primary way to build a type, also add an inherent new() or from_foo(). G#from-impls
  • An all-pub-field options struct implements Default so callers can write Opts { 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] on exhaustive_enums/exhaustive_structs for 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, use if let or let 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, then proc_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 local for<'a>, at most two of them. G#lifetimes
  • Generic parameters get short names that hint at their main bound (R: Read). Use T/U only for nearly unbounded APIs such as Vec<T>. G#generic-parameters
  • Constructors are new(), new_with_foo() for alternatives, and try_new() when they return a Result. Conversions are from_foo() backed by a From impl. 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), and with_foo(|foo| ..) / with_mut_foo(..). Use the closure form when the field sits behind a Mutex or RefCell (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(): no ctor crate, 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 for that 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 with format! rather than str::replace or 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:

TopicCommon adviceGoogle
Import granularityimports_granularity = "Crate""Module" (G#use-statement). group_imports and reorder_imports can stay
unwrap/expect, panicsNo unwrap() or expect() outside tests; avoid panic! in productionpanic! 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 combinatorsPrefer iterator chains over for loops, including chains in a for headerfor when clearer, never a chain in the header, avoid for_each (G#combinator-chains)
impl Into<String> paramsUse them in constructors and builders everywhereLibrary-only, and only when ergonomics are paramount (G#accepting-into)
Shared stateArc<Mutex<T>> / Rc<RefCell<T>> for shared mutable stateUnique ownership first; Rc/Arc only when the alternative is unreasonably complex (G#avoid-rc-arc)
TraitsDesign trait hierarchies; add extension traits to any type, such as StringExt for strA 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 typesthiserror enums carrying String payloadsCopy whenever possible (G#important-traits). Give a variant a payload only when the caller needs it
clippy::nurseryEnable -W clippy::nurseryAllow 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 librarieswarn plus #[expect] on trivial items (G#writing-doc)
All versions