rust-best-practices
Write idiomatic, robust, maintainable Rust. Use this skill whenever you write, review, refactor, debug, or explain Rust code: new crates, modules, functions, structs, traits, error types, async code, unsafe or FFI code, Cargo.toml, tests, or rustdoc.
Install / Use
npx skills add morganlinton/BeautifulRust --skill rust-best-practicesInstalls into whichever agent you are using.
SKILL.md
Installable skill definition
Quality Score
Category
Development & EngineeringSupported Platforms
Our assessment of rust-best-practices
rust-best-practices scores 92/100 on our quality scale, 901st of 4,645 Development & Engineering skills we index (top 20%).
Its SKILL.md is 29 KB long, well organised into 22 sections with 2 code examples: a thorough specification that gives an agent plenty to work with.
With 1,611 GitHub stars, it is one of the more widely adopted skills in the catalogue.
Maintenance, license and trust
- The repository was last updated 7 days ago, so rust-best-practices is actively maintained.
- It is released under the MIT license, a permissive license that allows use, modification and commercial use with attribution.
- Its trust signals score 100/100, with no cautions. These come from repository metadata, not a code audit — read the skill file before letting an agent act on it.
Safety scan
No issues foundOur scan of the whole file found no instruction hijacking, hidden characters, credential access, data exfiltration or destructive commands.
Automated pattern scan on 2026-11-07. It catches known dangerous patterns, not every risk — read a skill before letting an agent act on it.
rust-best-practices compared with similar skills
All 4 of these similar skills score higher than rust-best-practices; compare them before choosing.
| Skill | Score | Stars | Updated | Format |
|---|---|---|---|---|
| rust-best-practices (this skill)by morganlinton | 92 | 1.6k | 7d ago | SKILL.md |
| taste-skillby Leonxlnx | 100 | 111.4k | 2d ago | CLAUDE.md |
| Agent-Reachby Panniantong | 100 | 109.8k | 1d ago | CLAUDE.md |
| headroomby headroomlabs-ai | 100 | 79.1k | today | CLAUDE.md |
| ai-job-searchby ai-job-search-dev | 100 | 70.2k | 2d ago | CLAUDE.md |
Frequently asked questions
- How do I install rust-best-practices?
- Run
npx skills add morganlinton/BeautifulRust --skill rust-best-practices. The install tabs above show the steps for each supported agent. - Which AI agents does rust-best-practices work with?
- It is written for Zed, as a SKILL.md file. Other agents that read the same format can often use it too.
- Is rust-best-practices safe to use?
- Our scan of the whole file found no instruction hijacking, hidden characters, credential access, data exfiltration or destructive commands. It is MIT-licensed and scores 100/100 on trust signals. Skills are instructions an agent will follow, so read the file before installing it and do not approve commands you do not understand.
- Is rust-best-practices still maintained?
- The repository was last updated 7 days ago, so rust-best-practices is actively maintained.
Skill content
View source on GitHubname: rust-best-practices description: >- Write idiomatic, robust, maintainable Rust. Use this skill whenever you write, review, refactor, debug, or explain Rust code: new crates, modules, functions, structs, traits, error types, async code, unsafe or FFI code, Cargo.toml, tests, or rustdoc. Trigger on any mention of Rust, cargo, crates, .rs files, the borrow checker, lifetimes, traits, clippy, or rustfmt, even when the user does not ask for "best practices" or "idiomatic" code. Synthesized from the Google Rust Style Guide, the Rust API Guidelines, Effective Rust, Rust Design Patterns, and Microsoft's Pragmatic Rust Guidelines.
Rust best practices
Distilled from five Rust style and design guides, with a digest of each in references/.
Where they disagree, this file says which to follow and why.
1. Mindset
- Write Rust-shaped code. Don't port C++, Java, C#, or Python constructs one-to-one.
Model errors with
Result, resources with ownership andDrop, polymorphism with traits and enums, and nullability withOption. Inheritance-flavored tricks (Derefto a "base" struct,Manager/Factorytypes,throw_if_nullhelpers) are smells. - Make invalid states unrepresentable. Push invariants into types: enums instead of bool flags and "must be X if Y" comments, newtypes for units and IDs, fallible constructors that validate once. Then the compiler enforces what a comment only requests.
- Application code is not library code. Library code (anything with external users)
follows the API-design rules below strictly. Application code should favor simplicity
and directness: concrete types,
Stringor&strrather thanimpl Into<String>parameters,anyhow-style errors,Vecreturn values, no speculative generality. - Let the tools do the work. rustfmt owns formatting, clippy owns idiom checks, the compiler owns memory safety. Spend your attention on design.
2. Before you hand back code
Run these and fix everything they report:
cargo fmt
cargo clippy --all-targets --all-features -- -D warnings
cargo test
cargo doc --no-deps # libraries: catches broken doc links and missing docs
- Fix a lint rather than silencing it. When a lint is genuinely wrong for one item, use
#[expect(clippy::name, reason = "...")]on the narrowest item.expectwarns when the lint stops firing, so stale suppressions get cleaned up. Use#[allow]only in generated code or macro output. - Never put
#![deny(warnings)]in source: new toolchains add warnings and break the build. Enforce warnings-as-errors in CI instead, withcargo clippy -- -D warnings. - Configure lints once in
Cargo.toml([lints]or[workspace.lints]), not with scattered crate attributes. If a project has none, suggestlints/lints.tomlandlints/clippy.tomlfrom https://github.com/morganlinton/BeautifulRust; the suppression lints in it needrust-version1.81 or later.
3. Ownership and borrowing
Prefer unique ownership. Reach for Rc/Arc only when shared ownership is part of
the design, and for RefCell/Mutex only when interior mutability is. They are not
failures, but they move checking from compile time to run time, so use them on purpose.
Don't clone to make the borrow checker stop complaining. A clone that exists only to end a borrow produces a silent copy whose later changes are lost. Try these first, in roughly this order:
- Extend a temporary with a named
let, or shorten a borrow with a block. - Split a long expression into typed intermediates so the failing step is visible.
- Move a field out of
&mut selfwithstd::mem::take(needsDefault),std::mem::replace, orOption::takeinstead of cloning it. - Split a large struct into smaller ones so disjoint fields can be borrowed separately. The compiler tracks disjoint field borrows inside one function, but not across method calls.
- Clone small, immutable data deliberately, and say so in a comment if it's not obvious.
- Use indices into an owning
Vec, orRc<RefCell<_>>withWeakback-pointers, as a last resort. Avoid self-referential structs.
// Move out of a &mut enum without cloning.
fn a_to_b(e: &mut MyEnum) {
if let MyEnum::A { name, x: 0 } = e {
*e = MyEnum::B { name: std::mem::take(name) };
}
}
Accept the most general borrowed type. &str not &String, &[T] not &Vec<T>,
&T not &Box<T>, Option<&T> not &Option<T>. Deref coercion then lets callers pass
owned values, borrows, or literals. Where you only read a path, string, or byte buffer,
impl AsRef<Path> / impl AsRef<str> / impl AsRef<[u8]> is fine on parameters (never
on struct fields; store PathBuf and String). Library code that only iterates should
take impl IntoIterator<Item = T>; application code may simply take &[T].
Take ownership only when you need it, and then take it by value. If a function stores
or consumes its argument, take T, not &T plus an internal .clone(). If it only
reads, take &T; if it mutates in place, &mut T. Return multiple values as a tuple or
struct, not through &mut out-parameters (reusing a caller-owned buffer, as
Read::read does, is the exception).
Prefer owned data in structs. A struct holding &'a T forces a lifetime onto every
container. Owning a String or Vec<u8> is usually simpler and rarely the bottleneck.
When a type does carry a lifetime, give it a descriptive name ('src, 'conn); '_ and
'a are fine on simple functions.
Freeze after setup. Build with let mut, then rebind let data = data; so later
code cannot mutate it by accident.
Keep Rc, Arc, Box, and RefCell out of public signatures. Take &T,
&mut T, or T and let callers decide how to own things. A service that must be shared
implements a cheap Clone by wrapping its state in Arc<Inner> internally.
4. Types and API design
Naming
| Item | Case | Notes |
|---|---|---|
| Types, traits, enum variants | UpperCamelCase | Acronyms are one word: Uuid, HttpClient |
| Functions, methods, modules, locals | snake_case | |
| Constants, statics | SCREAMING_SNAKE_CASE | |
| Type parameters | short, from the main bound | T, R: Read, W: Write |
| Lifetimes | short lowercase | descriptive when on a type |
- No
get_prefix on getters:fn len(&self),fn name(&self) -> &str,fn name_mut(&mut self). Use baregetonly when there is one obvious thing to get (Cell::get). Related:set_foo(T),take_foo(),with_foo(|f| ..)for closure access, and unsafe_uncheckedsiblings for checked accessors. - Conversion prefixes signal cost and ownership:
as_(free, borrow to borrow),to_(expensive, borrow to owned or borrow to borrow),into_(consumesself). Putmutin type order:as_mut_slice, notas_slice_mut. - Constructors:
new(primary),with_capacity/new_with_foo(variants),try_new(fallible),from_foo(conversion; pair important ones with aFromimpl). I/O resources use domain verbs:File::open,TcpStream::connect. iter/iter_mut/into_iterreturn types namedIter/IterMut/IntoIter;keys()returnsKeys.- Error types read verb-object-error:
ParseBoolError, notBoolParseError. - Avoid weasel suffixes
Service,Manager,Factory,Helper,Util. Name the thing it is (Bookings,Dispatcher,FooBuilder). Don't repeat the module name in the type (foo::Id, notfoo::FooId). Avoid abbreviations an outsider wouldn't know. - If you implement
len(), implementis_empty(). - Feature names are bare:
std,serde,alloc, neveruse-stdorno-std.
Model the domain with types
- Newtypes for semantic distinctions:
struct Miles(f64)andstruct Kilometers(f64)cannot be mixed, where a type alias can. Add#[repr(transparent)]when layout matters, and derive or forward the traits you need (derive_morecuts the boilerplate). A newtype that encodes an invariant (Month) validates in a fallible constructor (TryFrom,FromStr,try_new) and never offers an infallibleFromfrom the raw type. - No
boolparameters in public or shared signatures.report(true)is unreadable at the call site and cannot grow a third mode. Use a two-variant enum (Verbosity::Summary,Verbosity::Full) or two methods (report(),report_verbose()):Widget::new(Size::Small, Shape::Round), notWidget::new(true, false). The only exception is a private helper whose call sites sit a few lines away. When refactoring, an existingboolparameter is one of the smells to remove, not a detail to preserve. - Named fields over tuples, except single-field newtypes and very local code.
Optionfor absence, never sentinels such as-1or"". Data-carrying enums instead of "field X is only meaningful when Y is set".- Use the strongest std type:
PathBuf/&Pathfor paths,Durationfor spans,NonZeroU32internally only (keep plain integers at API boundaries). - Independent flags use the
bitflagscrate; an enum models exactly one choice.
Derive and implement the standard traits eagerly
Downstream crates cannot add std traits to your types (orphan rule), so you must. Derive
whenever every field supports it: Debug (on every public type; hand-write it to redact
secrets), Clone (except unique resources and key material), Copy (only small
bitwise-copyable types, because the compiler copies silently), PartialEq / Eq,
PartialOrd / Ord (never ship PartialOrd without Ord when a total order exists),
Hash (consistent with Eq), Default. Implement Display for anything meant to be
read: error types, string-like wrappers. Add #[must_use] to pure functions and to
types, such as errors and lazy values, whose discarding is a bug.
Implement From / TryFrom, never Into / TryInto directly (blanket impls give you
those). Prefer from / into over as, which silently allows lossy casts. Implement
FromStr rather than an ad-hoc from_str method, FromIterator and Extend on
collections, and IntoIterator for both owned and borrowed forms. Implement Deref only
on smart pointers and owning collections (Vec<T> to [T]), never to fake inheritance.
Overload operators only for their mathematical meaning.
Don't put derivable trait bounds on struct definitions: #[derive(Clone)] struct Good<T>,
not struct Bad<T: Clone + Debug>. Bounds belong on the impl blocks that need them.
If you hand-write Ord, hand-write PartialEq, PartialOrd, and Hash to agree with it
(compare the same fields, ignore the same fields). A derived PartialEq next to a custom
Ord breaks the std contract and misbehaves in BTreeMap and sort/dedup. Say in the
docs which fields equality ignores.
Constructors, builders, defaults
- Provide
new()even whenDefaultexists, and make them agree (one delegates to the other). Derive or implementDefaultso callers can write..Default::default(); bag-of-options structs with public fields should always have it. - Up to two optional parameters: inherent constructors (
new,with_a,with_b). Beyond that, or when construction validates or has side effects, add a builder:Foo::builder()returnsFooBuilder, setters are named after fields (timeout(d), notset_timeout) and infallible, andbuild() -> Result<Foo, Error>does all validation including cross-field checks. Prefer non-consuming setters (&mut self -> &mut Self) unlessbuildmust move a value out. - A constructor with four or more loose arguments should group them into semantic types:
Deposit::new(account, amount)rather than four&str/u64parameters.
Visibility and evolution
- Private by default. Fields stay private unless the struct is passive C-style data.
Widen to
pub(crate)orpub(super)beforepub. Making an ite
Truncated for display — read the full file on GitHub.
Related Skills
taste-skill
111.4kAgent skills that give coding agents taste: art direction, reference images and a browser check instead of generic AI pages
Agent-Reach
109.8kGive your AI agent eyes to see the entire internet. Read & search Twitter, Reddit, YouTube, GitHub, Bilibili, XiaoHongShu — one CLI, zero API fees.
headroom
79.1kCompress tool outputs, logs, files, and RAG chunks before they reach the LLM. 20% fewer tokens for coding agents, 60-95% fewer tokens for JSON, same answers. Library, proxy, MCP server.
ai-job-search
70.2kThe job search that runs on your machine. AI job application framework built on Claude Code: evaluate postings, tailor CVs, write cover letters, prep interviews. Fork it and own it.
Languages
Trust signals
From repository metadata: license, adoption, age and documentation. Not a code audit — see the Safety scan above for what the skill file itself contains.
