agentsclimarketplace

Rust expert

Skill Akayashuu/agent-skills/skills/rust-expert

Framework & language expert skills for Claude Code — idiomatic best practices for TypeScript, React, Vue, Svelte, Solid, Angular, Astro

Install
npx -y skills add Akayashuu/agent-skills --skill rust-expert

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Use when writing, reviewing, or refactoring Rust — ownership/borrowing vs reflexive clone(), error handling with Result/? and thiserror vs anyhow, avoiding unwrap()/expect() in production, traits & generics vs dyn, lifetimes as a design signal, Option/Result combinators, and isolating unsafe.

SKILL.md

8.7 KB, as published. Nobody here has run it

Rust Expert

Overview

Idiomatic Rust is borrow before you clone, encode errors in the type, and make illegal states unrepresentable. The compiler enforces memory and thread safety; your job is the design it can't check. Most real churn in a Rust codebase isn't fighting the type system — it's clone() sprinkled to silence the borrow checker, unwrap() on genuinely fallible paths, and dyn Trait reached for where a generic fits cleanly. These are the judgment calls a linter can't make for you.

Quick Reference

GoalDoAvoid
Pass data to a functionborrow (&T, &[T], &str)take owned T/Vec/String then clone in
Quiet the borrow checkerrestructure scopes/split borrowsreflexive .clone() to dodge it
Recover from failurereturn Result<T, E>, propagate with ?unwrap()/expect() on fallible paths
Define a library errorenum impl Error (or thiserror)Box<dyn Error> as your public API
Aggregate app errorsanyhow::Result + .context()hand-rolled enums in a binary's main
Abstract over typesgeneric <T: Trait> bounddyn Trait when the type is known statically
Heterogeneous collectionVec<Box<dyn Trait>>generics (can't hold mixed types)
Handle Option/Resultmap/and_then/unwrap_or_elsedeeply nested match
Reach for unsafetiny module with a safe wrapper + // SAFETY:scattered unsafe blocks
Model statedistinct variants/typesa bool/Option soup that allows nonsense

Borrow before you reflexively clone

Default to borrowing: take &str not String, &[T] not Vec<T>, and return borrows tied to an input lifetime when you can. A .clone() is sometimes the right call (cheap Copy types, escaping a borrow conflict deliberately, Arc::clone for shared ownership) — but reaching for it the instant the borrow checker complains usually means the real fix is restructuring: shorten a borrow's scope, split a struct so fields borrow independently, or take &mut and mutate in place instead of consume-and-return. See examples/ownership.rs.

// ❌ forces every caller to own a Vec, then clones again inside
fn first(items: Vec<String>) -> String { items[0].clone() }
// ✅ borrow in, borrow out — zero allocations
fn first(items: &[String]) -> Option<&str> { items.first().map(String::as_str) }

Result, ?, and thiserror (lib) vs anyhow (app)

Model fallibility in the type: return Result<T, E> and let ? propagate, converting through From as it unwinds. For a library, define a concrete error enum so callers can match on variants — thiserror derives Display/Error/From for you, but a hand-written impl Error is the same shape (see examples/errors.rs). For an application or binary, where you just need a rich message and a backtrace, anyhow::Result<T> with .context("doing X") is the pragmatic choice — don't make callers match errors they'll only print. The rule of thumb: thiserror when someone programmatically inspects the error, anyhow when a human reads it.

No unwrap()/expect() on fallible paths

unwrap() and expect() turn a recoverable error into a panic — fine in tests, examples, and prototypes, dangerous in a service that should degrade gracefully. In production code, propagate with ? or handle explicitly with unwrap_or, unwrap_or_else, or a match. Reserve panics for genuine invariant violations ("this slice is non-empty by construction"), and even then prefer expect("why this can't fail") so the message documents the assumption. unwrap() on Option from indexing or HashMap::get is a frequent crash source — use ? (via ok_or) or a combinator instead.

Generics + trait bounds vs dyn Trait

Prefer static dispatch: a generic fn parse<R: Read>(r: R) is monomorphized, inlinable, and zero-cost. Reach for dyn Trait (trait objects) only when you genuinely need runtime heterogeneity — a Vec<Box<dyn Draw>> of mixed concrete types, a plugin boundary, or to cut compile-time/binary bloat from excessive monomorphization. impl Trait in argument and return position covers the common "I just want to abstract over one type" case without naming it. Don't use a trait object to "keep it flexible" when every call site knows the concrete type — that's a vtable indirection bought for nothing.

fn total(shapes: &[Box<dyn Shape>]) -> f64 { shapes.iter().map(|s| s.area()).sum() } // needs dyn: mixed types
fn area(s: &impl Shape) -> f64 { s.area() } // static dispatch, one type, zero cost

Lifetimes as a design signal

Lifetime annotations describe relationships the compiler already tracks; most functions need none thanks to elision. When you find yourself fighting lifetimes — especially storing a borrow inside a long-lived struct, or returning a reference that outlives its source — treat it as a signal that an ownership boundary is misplaced, not as a puzzle to annotate your way out of. Often the cleaner answer is to own the data (store String, not &str), use Arc/Rc for genuine shared ownership, or use an index/id instead of a back-reference. A struct full of lifetime parameters is usually telling you it's holding borrows it should own.

Option/Result combinators over nested match

For transforming and chaining, combinators read better than pyramids of match: map, and_then, or_else, unwrap_or_else, ok_or, ?, and if let/let ... else for early returns. Reserve match for cases where you genuinely branch on several variants with distinct logic. See display_name in examples/errors.rs.

// ❌ nested match noise
let name = match find(id) { Some(u) => match u.name { Some(n) => n, None => "?".into() }, None => "?".into() };
// ✅ combinator chain
let name = find(id).and_then(|u| u.name).unwrap_or_else(|| "?".into());

Isolate unsafe

unsafe doesn't disable the borrow checker; it lets you do five specific things (deref raw pointers, call unsafe fns, etc.) whose invariants you now guarantee. Keep it in the smallest possible module behind a safe API, and document every block with a // SAFETY: comment stating why the invariant holds. The goal is that the rest of the codebase — and reviewers — only audit a tiny, clearly-marked surface, not chase soundness through scattered blocks. If you can express it safely (slices, iterators, Cell/RefCell), do that instead.

Common Mistakes

  • Reflexive .clone() to silence a borrow error — restructure the borrow first.
  • Owned params (String/Vec<T>) where &str/&[T] would do — forces allocation on callers.
  • unwrap()/expect() on I/O, parsing, or map lookups in production — propagate with ?.
  • Box<dyn Error> as a library's public error type — give callers an enum they can match.
  • dyn Trait where the concrete type is known — pay no vtable cost; use a generic or impl Trait.
  • Lifetime-parameter soup in structs — usually means it should own the data or use Arc/an id.
  • Nested match on Option/Result — reach for combinators and ?.
  • Scattered unsafe without // SAFETY: notes — concentrate it behind a safe wrapper.
  • Stringly-typed state (bools/Options that allow impossible combinations) — encode it as an enum.

When NOT to over-engineer

A script, test, or throwaway prototype is allowed to unwrap() and clone() freely — the discipline above is for production code that must not panic and runs hot. Don't add lifetimes, generics, or trait abstractions speculatively; introduce them when a second concrete type, a real performance need, or an actual API boundary appears. A little duplication or one well-placed clone is cheaper than a wrong abstraction. The standard library is the style guide — read how std::io, Result, and Iterator are shaped and imitate their restraint.

Sources

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.