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. 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.
How do I install this agent skill?
npx skills add https://github.com/morganlinton/beautifulrust --skill rust-best-practicesIs this agent skill safe to install?
- Gen Agent Trust Hubpass
This skill provides a set of idiomatic Rust guidelines and best practices synthesized from official and well-known sources. It helps the agent write, review, and refactor Rust code using standard tools like cargo, clippy, and rustfmt. No security issues or malicious patterns were detected.
- Socketpass
No alerts
- Snykpass
Risk: LOW · No issues
What does this agent skill do?
Rust best practices
Distilled from five sources, with full per-source digests in references/: the Google
Rust Style Guide, the official Rust API Guidelines, Effective Rust (David Drysdale),
Rust Design Patterns (rust-unofficial), and Microsoft's Pragmatic Rust Guidelines.
Where the sources 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. The compiler then enforces what a comment could only request.
- 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,
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 new warnings and will break the build. Enforce warnings-as-errors in CI instead (cargo clippy -- -D warningsorRUSTFLAGS="-D warnings"). - Configure lints once in
Cargo.toml([lints.rust],[lints.clippy], or[workspace.lints]), not with scattered crate attributes. The BeautifulRust repo ships a ready-made set inlints/lints.tomlandlints/clippy.toml(https://github.com/morganlinton/BeautifulRust); suggest it when a project has none. A good baseline:unsafe_op_in_unsafe_fn,missing_debug_implementations,clippy::all, and where the project tolerates themclippy::undocumented_unsafe_blocksandclippy::allow_attributes_without_reason.
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 item more public later is non-breaking; the reverse is breaking, and everypubitem is a promise. -
Mark public enums and public-field structs that cross crate boundaries
#[non_exhaustive]unless the set is a stable ontology. When matching one, list the variants you handle and add a wildcard arm. -
Seal traits you don't want implemented downstream, and say so in the docs:
pub trait TheTrait: private::Sealed { /* ... */ } mod private { pub trait Sealed {} impl Sealed for usize {} } -
Hide implementation iterator types behind
impl Iterator<Item = T>(internal APIs) or a newtype (public APIs you must keep stable). -
Expose each public item at exactly one path. Don't
pub usea foreign crate's items unless you are an umbrella crate. Avoid leaking third-party types into your public API; feature-gateserdeimpls behind a feature namedserde. If a dependency's type must appear, re-export it so callers can name the matching version. -
Mark a function
constonly if it will stayconst. Don't exposeErrorKindenums or other internals that lock in failure modes you consider private.
Traits and generics
- Define a trait only for generic programming (
T: Trait,impl Trait,dyn Trait) or as an extension trait (FooExt). Don't use traits merely to organize code. Put essential functionality in inherent methods and have trait impls forward to them, so callers need no import. - Prefer, in order: concrete types; generics (
T: Trait,impl Trait);dyn Trait, ideally wrapped in your own struct (struct DynStore(Arc<dyn Store>)). Generics give static dispatch and inlining; trait objects give heterogeneous collections and smaller binaries. Don't nest generics on service-level types (Service<Backend>is fine;Service<Backend<Store>>is not). - Decide early whether a trait must be object-safe. Generic methods or
Selfoutside the receiver break it; exclude such methods withwhere Self: Sized. - Keep required trait methods minimal and provide defaults for everything derivable from
them, as
Iteratordoes fromnext. - If a function has a clear receiver, make it a method. A computation with no receiver is
a free function, not
Database::check(params). Associated functions are for construction. - Closures: use the most general bound that works (
FnOnceif called once,FnMutif it mutates, elseFn) and preferFn*bounds to barefnpointers. Closure parameters go last; don't take more than one. - Keep parameter order consistent across a crate: call-specific first, ubiquitous context (loggers, allocators) last.
- Reach for a macro only when functions, generics, and traits cannot do the job. Prefer
macro_rules!to proc macros, make macro input look like the output it generates, and never let a macro change a type's kind or a function's signature.
Refactoring someone else's code
- Fix every smell the request is about, not just the cheap ones: clones that only
satisfy the borrow checker,
&String/&Vec<T>parameters,unwrapon fallible paths,Rc<RefCell<_>>in signatures,get_getters,boolparameters,+string concatenation,#![deny(warnings)], error types withoutError, nondeterministicHashMapiteration in output. - Keep public behavior identical unless changing it is the point: the same operations,
the same success and failure semantics, the same output bytes. Turning a panic into an
Erris the change callers want. Dropping an error variant, removing a derive, or changing what a constructor takes is not; if a signature must change, show the before and after in the change list and say why. - Preserve quirks you don't understand and flag them, rather than silently "fixing" them.
- When the module has no tests, pin the current behavior with a few before you touch it.
5. Errors and panics
Pick the mechanism by contract:
| Situation | Use |
|---|---|
Absence is normal (first(), checked_add) | Option<T> |
| The caller may need to know what went wrong | Result<T, E> |
| A bug or violated invariant inside your code | panic! / assert! / expect("why") |
Panics are not exceptions. A panic means "stop now". Never use one to report bad
input, never rely on catch_unwind for control flow (it does nothing under
panic = "abort" and can leave state inconsistent), and never catch a panic and continue
inside a library. Where a panicking convenience is justified, pair it with a fallible API
(get / get_unchecked, new / try_new) and document it under # Panics.
Every intentional panic carries a message with the relevant values:
assert!(buf.len() >= HEADER, "buffer too small: {} bytes", buf.len()),
expect("config is loaded before workers start"). Prefer expect("reason") to
unwrap(). Don't restate the condition as the message.
Pattern-match instead of check-then-unwrap: if let Some(v) = opt { .. } or
let Some(v) = opt else { return .. };, never if opt.is_some() { opt.unwrap() }.
Lock poisoning is fatal: .lock().expect("lock poisoned"). Don't recover with
into_inner.
Library error types (any E in a public Result):
- Implement
std::error::Error(which requiresDebug+Display), beSend + Sync + 'static, and implementsource()for wrapped causes. Never use(),String, orBox<dyn Error>as a library's public error type. Displaymessages are lowercase, concise, and have no trailing punctuation:"invalid header length".- Implement
From<Upstream>so?converts. Usemap_erronly for foreign errors or to add context. - Two sound shapes: an enum with one variant per cause (
thiserrorgenerates this well), or a struct per situation with a privatekindplusis_xxx()and accessor methods. Either way, don't make one crate-wide enum that lists every failure mode in the crate, and don't expose details callers cannot act on. - If a fallible function consumes an argument, return it inside the error so the caller
can retry without cloning (
FromUtf8Error::into_bytes). - Never expose
anyhow::Errorin a library's public API.
Application error handling: anyhow or eyre with .context("opening config") is
the right tool. Use one app-level error type, not several. main may return
Result<(), anyhow::Error>.
Destructors never fail, panic, or block. Provide close(self) -> Result<()> for
fallible teardown and let Drop do best-effort cleanup.
Document failure modes: # Errors for any Result, # Panics for any reachable
panic, # Safety for every unsafe fn.
6. Everyday idioms
-
Iterators over hand loops when the pipeline is short and reads top-down:
values.iter().filter(..).map(..).collect(),.any(),.sum(),.fold(). Collect fallible steps intoResult<Vec<_>, E>and apply?. Keep an explicitforloop when the body is large, does several jobs, or needs awkward early returns. Bind a long chain to a named variable rather than putting it in aforheader, and preferfor x in itto.for_each(|x| ..). -
Option/Resultcombinators (map,and_then,ok_or_else,unwrap_or_else,as_ref,as_deref) when they're clearer thanmatch, andmatchwhen they're not. -
format!/write!with inline args (format!("{name}: {count}")) for concatenation and substitution, not chained+orstr::replace.write!into an existing buffer in hot paths. -
Explicit types where inference obscures intent: on
.into(),.parse(),.collect(), and complex iterator results. Preferlet x: Vec<u32> = ..to a turbofish. -
Drop guards for cleanup that must run on early return or unwind. Bind them to a name (
let _guard = ..), becauselet _ = ..drops immediately. -
Closures that capture mixed ownership: rebind in a block, then
move:let job = { let cfg = cfg.clone(); move || run(cfg, data) }; -
Import style: one
useper module path (use foo::bar::{Bar, Baz};); types and traits imported and used unqualified (Regex); free functions qualified by their module (mem::take,fs::read). Import a trait inside the function that needs it unless the whole file uses it. No glob imports exceptuse super::*in test modules anduse Enum::*inside a function that matches on it. -
Magic numbers become named
consts with a doc comment saying where the value comes from and what changing it affects. -
Logging: structured events through
tracingorlog, notprintln!/dbg!, except for a CLI's own stdout output.
7. Concurrency and async
-
Public types, and the futures they return, should be
Send(and usuallySync). Pin it with a compile-time check so a refactor can't silently regress it:const _: () = { const fn assert_send<T: Send>() {} assert_send::<MyClient>(); };Holding an
Rcor aRefCellborrow across an.awaitmakes a future!Send. -
Prefer message passing (channels) to shared state. When sharing is necessary: keep data with linked invariants under one lock, keep lock scopes to a few lines, never call user closures while holding a lock, and never return a
MutexGuardfrom a public method. -
Write
async fnrather thanfn -> impl Future, except in traits or when setup must run outside the async block for a hot path. -
Long CPU-bound work inside async must yield (
tokio::task::yield_now().awaitevery tens of microseconds) or move tospawn_blocking. -
Library code doesn't spawn raw threads or assume a runtime; take an executor, a spawn function, or
impl AsyncRead/impl Readfrom the caller.
8. Unsafe and FFI
- Write
unsafeonly for one of three reasons: a novel abstraction safe code can't express, a benchmarked performance need, or FFI and platform calls. Reach for std and well-reviewed crates first; they already contain the auditedunsafe. - Every
unsafeblock or impl gets a// SAFETY:comment stating why the invariants hold. Everyunsafe fngets a# Safetydoc section listing what the caller must uphold. Enableunsafe_op_in_unsafe_fn. - Keep
unsafein the smallest module that can uphold its invariant and wrap it in a safe API; the module boundary is the soundness boundary. A safe function that can cause undefined behavior through any sequence of safe calls is unsound, and unsound code is never acceptable. - Mark a function
unsafeonly when misuse can cause undefined behavior, not because it is dangerous (delete_database()is a safe function). - No
transmuteto cast enums or dodge lifetimes, and nounsafe impl Sendto silence the compiler. - Test unsafe code under Miri and with adversarial inputs.
- FFI:
#[repr(C)]on shared structs, sized integer types,CString/CStrfor strings, allocate and free on the same side, wrap C-owned pointers in an RAII type whoseDropfrees them, check for null and returnResult, and stop panics at the boundary withcatch_unwind. Generate bindings withbindgenin afoo-syscrate and keep business logic in a safe core crate.
9. Crates, modules, dependencies
- Prefer small, single-purpose crates in a flat
crates/workspace; never nest a crate inside another crate'ssrc/. Split when a crate grows a second purpose or a subdirectory. Share versions and lints through[workspace.dependencies]and[workspace.lints]. - Modules: name a leaf module after its primary item (
Foolives infoo.rs). Non-leaf modules contain onlymoddeclarations and re-exports. Put the essential items at the crate root and group the rest by use case (account,network), not by kind (traits,errors). Don't define a prelude. - Re-export individually (
pub use foo::{A, B};), never with a glob. - Dependencies: every one costs compile time, binary size, and supply-chain exposure,
including
build.rsand proc macros. Use ranges ("1","1.4"), never"*"or a needless"=". CommitCargo.lockfor binaries. Audit withcargo tree --duplicates,cargo deny,cargo udeps, andcargo audit. - Features are additive and bare-named:
std,serde,alloc,test-util. Neverno-std,use-serde, or a feature that removes or changes a public item. Don't feature-gate struct fields or trait methods. Put mocks and fakes behindtest-util. - Semver: breaking changes include removing or renaming public items, adding a
variant to an enum that isn't
#[non_exhaustive], adding a field to a publicly constructible struct, adding a trait bound, or making a trait non-object-safe. Deprecate in a minor release before removing in a major one. Use the latest stable edition for new crates and set an MSRV deliberately. - Don't run code before
main()(noctor-style crates). Don't use unstable features on a stable toolchain.
10. Documentation and comments
///on every public item;//!at the top of every public module and the crate root. The first sentence is a single line of about fifteen words, because it becomes the summary in listings.- Sections, in this order, as applicable:
# Examples,# Errors,# Panics,# Safety. Examples use?with thefn mainhidden behind#lines, neverunwrap(), because users copy them verbatim. Describe parameters in prose, not a table. - Document what the signature can't say: preconditions, side effects, ownership
transfer, complexity, and why. Don't restate the type or the name;
/// Removes an element.onremove_elementis noise. - Link items with
[`Type`]. Hide impls users never trigger (From<PrivateError> for PublicError) with#[doc(hidden)]. //comments explain why, not what:// SAFETY:,// TODO(issue-123):, non-obvious magic values, workarounds with a link. No design journals, "why we chose X" essays, or rule-compliance tables in code docs. Document the end state.
11. Tests
- Unit tests in
#[cfg(test)] mod tests { use super::*; .. }for private internals. Integration tests intests/for anything reachable through the public API, and prefer that location when either would do. Doc examples are tests too. - Test observable behavior and properties (monotonic, round-trips, idempotent), not restated constants or mirrored branches.
- Write a failing test before fixing a bug. Cover each feature combination and each platform-specific path.
- Anything doing I/O, time, randomness, or network must be mockable:
new_mocked() -> (Self, MockCtrl)behindtest-util, not aDefaultthat performs I/O. - Fuzz parsers of untrusted input with
cargo fuzz. Benchmark withcriterionordivanandstd::hint::black_box. Run Miri overunsafe. - Assertions get context so failures read well:
assert_eq!(got, want, "input {input:?}").
12. Performance, in order
Design for usability first and optimize after measuring. When you do: Vec::with_capacity
or collect() over push loops; reuse caller-owned buffers in hot APIs
(fn read_into(&mut self, buf: &mut Vec<u8>)); Box<[T]>, Box<str>, Arc<str> for
long-lived immutable sequences; shrink_to_fit on long-lived collections built without a
reservation; a faster hasher (foldhash, FxHash) only for trusted keys; batch work
instead of per-item locking; applications may set a #[global_allocator] such as
mimalloc. Derive Copy for small plain types and never for large ones.
13. Final checklist
Before returning Rust code, confirm:
cargo fmt,cargo clippy -- -D warnings, andcargo testare clean.- No
unwrap()outside tests andmain; everyexpect,assert, andpanichas a message with the relevant values. - Public functions take
&str,&[T],impl AsRef<_>, orimpl IntoIterator, not&Stringor&Vec<T>. - Every public type derives
Debugand the applicable common traits; error types implementError + Display + Send + Sync + 'static. - No
boolparameters in public signatures; no hand-writtenOrdwithout matchingPartialEqandHash. - No
.clone()whose only job is to satisfy the borrow checker. - No glob imports, no preludes, no glob re-exports, no leaked third-party types.
- Every
unsafeblock has a// SAFETY:comment; everyunsafe fnhas# Safetydocs. - Public items have docs with a one-line summary and
# Errors/# Panicswhere relevant. - Application code stayed simple; library code followed the API rules.
References
Read these when you need the reasoning behind a rule or a complete checklist for a task. Each is a condensed digest of one source.
references/rust-api-guidelines.md(https://rust-lang.github.io/api-guidelines/): designing a public API or crate; all 54 checklist items with conventions.references/effective-rust.md(https://www.lurklurk.org/effective-rust/): choosing between alternatives: generics vsdyn,Resultvs panic, iterators vs loops, borrow-checker tactics, dependencies, CI.references/rust-design-patterns.md(https://rust-unofficial.github.io/patterns/): idioms, patterns, and anti-patterns with code.references/microsoft-rust-guidelines.md(https://microsoft.github.io/rust-guidelines/): error struct design, panic policy, unsafe policy, async, workspace layout, performance.references/google-rust-style-guide.md(https://google.github.io/styleguide/rustguide.html): formatting, imports, module layout, accessor naming, application vs library.
How can the creator link this skill?
Add the canonical catalog link to the repository README so users can inspect current installs and available audits. The publishing guide covers the complete discovery path.
<a href="https://skillzs.dev/skills/morganlinton/beautifulrust/rust-best-practices">View rust-best-practices on skillZs</a>