rust-patterns
Writes and reviews idiomatic Rust - ownership and borrowing decision rules for API signatures, lifetime avoidance strategies, thiserror vs anyhow error design, iterator-first style, and when Rc/RefCell/Arc/Mutex are actually justified - with good/bad code pairs. Use when someone asks "why won't this borrow check", "should this take &str or String", "thiserror or anyhow", "how do I share state between threads in Rust", or their code is a wall of clone() calls. Do NOT use for cross-language migration planning - use language-version-migrator instead - and do NOT use for generic API endpoint design - use api-design instead.
- License 许可
- MIT
- 最近更新
- 2026-10-14
- Stars
- ★ 25
- Forks
- 3
01 前言 Front Matter
- name
- rust-patterns
- metadata
- { "title": "Rust Patterns" }
- description
- Writes and reviews idiomatic Rust - ownership and borrowing decision rules for API signatures, lifetime avoidance strategies, thiserror vs anyhow error design, iterator-first style, and when Rc/RefCell/Arc/Mutex are actually justified - with good/bad code pairs. Use when someone asks "why won't this borrow check", "should this take &str or String", "thiserror or anyhow", "how do I share state between threads in Rust", or their code is a wall of clone() calls. Do NOT use for cross-language migration planning - use language-version-migrator instead - and do NOT use for generic API endpoint design - use api-design instead.
03 SKILL.md 正文
Rust Patterns
The borrow checker is a design-feedback tool: when it fights you, the ownership story of the code is unclear, and the fix is almost always restructuring, not sprinkling clone() until it compiles. The costly mistake this skill prevents is exactly that clone-and-unwrap() dialect - code that compiles, ships, then panics in production or silently copies megabytes per request.
Operating procedure
Step 1: Gather inputs
- Binary or library? Libraries need typed errors and conservative signatures; binaries can be looser.
- Threading/async story: single-threaded, threaded, or async (which runtime - assume Tokio unless told otherwise).
- Performance sensitivity: hot path or glue code. Label guesses as guesses; don't optimize glue.
- Existing conventions (error crate in use, MSRV constraints stated by the user).
Step 2: Decide ownership per signature - the four-question rule
For every function parameter, in order:
- Only reads? Take
&T- and prefer the borrowed view type:&strover&String,&[T]over&Vec<T>,&Pathover&PathBuf. This makes the API maximally callable. - Mutates in place? Take
&mut T. - Stores or consumes the value (moves it into a struct, sends it to a channel, returns it)? Take
Tby value - taking&Tand cloning inside hides the cost from the caller; takingTlets callers who are done with the value move it for free. - Sometimes borrows, sometimes owns?
impl Into<String>/Cow<'_, str>for ergonomic APIs - but only at public boundaries; internally, pick one.
Return owned values from constructors. Default to borrowing everywhere else. If a signature needs a lifetime annotation you can't explain in one sentence, restructure instead: most lifetimes should stay elided.
Step 3: Choose the sharing tier - escalate only on proof
Work down this ladder and stop at the first rung that works; each step adds runtime cost and API noise.
- Plain ownership + borrows (the default, ~95% of code).
Rc<T>- single-threaded shared ownership (e.g. a graph node referenced by several parents).Rc<RefCell<T>>- add interior mutability; runtime borrow panics replace compile errors, so keep borrow scopes tiny.Arc<T>/Arc<Mutex<T>>(orRwLock) - cross-thread. In async code, never hold astd::sync::Mutexguard across an.await; use the runtime's mutex or restructure so the guard drops first.
Prefer owning data in structs over storing references with lifetimes - a struct with a lifetime parameter infects every type that contains it. Self-referential structs are a design smell: use indices into a Vec, or an arena, not Pin gymnastics.
Step 4: Design errors by audience
- Library:
thiserror- a typed enum,#[error(...)]messages,#[from]for source errors. Callers must be able to match on variants. - Application:
anyhow-Result<T, anyhow::Error>with.context("reading config")at each layer, so failures carry a breadcrumb trail. ?for all recoverable errors;panic!/expectonly for invariant violations that indicate a bug, andexpect("why this cannot fail")with a reason, never bareunwrap()on production paths.
use thiserror::Error;
#[derive(Error, Debug)]
enum AppError {
#[error("not found: {0}")]
NotFound(String),
#[error(transparent)]
Io(#[from] std::io::Error),
}
fn load(path: &str) -> Result<String, AppError> {
Ok(std::fs::read_to_string(path)?)
}
Step 5: Apply the idiom checklist
- Iterators and combinators (
map,filter,collect) over index loops - they eliminate bounds-check noise and off-by-ones. Option/Resultcombinators (map,and_then,ok_or,unwrap_or_else) over nestedmatch; but amatchis still clearer than a four-combinator chain.- Newtypes for domain values (
struct UserId(u64)) so the compiler catches transposed arguments; implementFromfor conversions; deriveDebug, Clone, PartialEqwhere sensible. - Make illegal states unrepresentable: an enum with
Connected(Session)/Disconnectedbeats a struct withis_connected: booland a nullable session. - Generics for hot-path polymorphism (monomorphized, inlined);
dyn Traitfor heterogeneous collections and to keep compile times and binary size down at cold boundaries.
Step 6: Performance and hygiene passes
- Grep for
clone(): each one is either justified (write a word of why), replaced by a borrow, or replaced by a move. Vec::with_capacitywhen size is known; avoid collecting intermediates you immediately iterate again.- Profile before optimizing anything beyond these defaults.
unsafestays tiny, wrapped in a safe API, with a// SAFETY:comment stating the invariant.cargo clippy -- -D warningsandcargo fmt --checkrun in CI, not just locally.
Worked artifact: signature and sharing, bad/good
Bad - fights the checker, hides costs, panics:
fn process(names: Vec<String>, cfg: &Config) -> Vec<String> {
let mut out = Vec::new();
for i in 0..names.len() {
let n = names[i].clone(); // clone to dodge a move error
out.push(format!("{}-{}", cfg.prefix.clone(), n));
}
out
}
let shared = Arc::new(Mutex::new(state)); // single-threaded code!
let v = map.get("k").unwrap().clone(); // panic + copy
Good:
fn process<'a>(names: impl Iterator<Item = &'a str>, prefix: &str) -> Vec<String> {
names.map(|n| format!("{prefix}-{n}")).collect()
}
let shared = Rc::new(RefCell::new(state)); // right tier for one thread
let v = map.get("k").ok_or(AppError::NotFound("k".into()))?;
The good version borrows what it reads, allocates only the output it must own, uses the cheapest sharing tier, and turns the panic into a typed error.
Deliverable
Produce code (or a review) where every signature is justified by the four-question rule, sharing primitives are at the lowest sufficient tier, the error strategy matches the audience (thiserror for libraries, anyhow for apps), and every remaining clone(), expect, and unsafe block carries its one-line justification.
Do NOT
- Do not
clone()to silence a borrow error - restructure ownership; the checker is pointing at a real design ambiguity. - Do not take
&String/&Vec<T>/&PathBufin signatures; the borrowed view types cost nothing and accept more callers. - Do not reach for
Arc<Mutex<T>>in single-threaded code, orRc<RefCell<T>>as a default - they are escalations, not conveniences. - Do not hold a sync mutex guard across
.await- it deadlocks or blocks the executor. - Do not
unwrap()on production paths;?orexpect("reason"). - Do not store references in structs when owning is feasible; lifetime parameters propagate to every containing type.
- Do not block the async executor with sync I/O; use the runtime's
spawn_blockingfor it.
Quality bar
cargo clippy -- -D warningsclean;cargo fmtclean.- No bare
unwrap()outside tests; everyexpectstates why it can't fail. - Every
clone()in the diff is annotated or eliminated. - Public API takes borrowed view types and returns owned values.
unsafeblocks each have a// SAFETY:invariant comment.
Escalation
For migrating a codebase between language versions or from another language, use language-version-migrator. Async architecture questions beyond Rust idiom (queues, services) route to microservices or kafka-pipelines; API surface design to api-design.
04 同仓库更多 Skills
有氧运动计划
zone-2-cardio-plan
当有人要求制定稳态有氧训练计划、进行有氧基础训练或达到第二区间、安排轻松或耐力型的每周有氧训练、按心率区间训练,或提高静息心率、最大摄氧量或耐力时,可以使用此方案。该方案构建了一个包含训练计划结构、训练方式选择和进度安排的心率区间有氧训练模块。本方案仅提供一般健身指导,不构成医疗建议。请勿用于设计力量或举重训练计划(请使用力量训练计划)、高强度间歇训练 (HIIT) 或循环训练(请使用体能训练和 HIIT 计划),或灵活性/柔韧性训练(请使用灵活性训练计划)。
youtube-script-writer
Writes retention-engineered long-form YouTube scripts on a hook/body/payoff architecture - a first-30-seconds hook that survives the retention cliff, a beat-sheet body with mini-hooks and bridges, timed pattern interrupts, and a payoff that over-delivers - with B-roll and editing cues marked inline. Use when someone says "script my next video", "my retention graph dies at the start", "tighten this draft", or "outline a video on X". Do NOT use for the 3-second opener of TikTok, Reels, or Shorts - use video-hook-writer instead; do NOT use for spoken voiceover synced to on-screen tutorial actions - use narration-script instead; do NOT use for a sales demo built on a buyer's discovery notes - use demo-script instead; do NOT use to design the thumbnail and title - use thumbnail-concept instead.
win-back-campaign
Designs segmented win-back email sequences for dormant users - dormancy tiers, a three-beat sequence with a single proportional incentive, and a suppression rule - and measures success by re-activation, not opens. Use when someone says "our users went quiet", "design a win-back campaign", "how do I re-engage lapsed customers", "should we email people who haven't logged in for months", or lifecycle messaging has stopped landing with a lapsed segment. Do NOT use for recovering an unfinished checkout - use abandoned-cart-sequence instead. Do NOT use for diagnosing and fixing why active customers cancel - use churn-reduction instead. Do NOT use for building always-on onboarding or nurture flows - use email-drip-builder instead.
wikipedia-style
Writes neutral, verifiable, encyclopedic prose following Wikipedia's core content policies and manual of style - NPOV, inline citations, notability-backed claims, and a strict ban on promotional language. Use when someone asks "write this like a Wikipedia article", "draft a Wikipedia page for this company or person", "make this neutral and encyclopedic", or "why does my article draft keep getting rejected". Do NOT use for persuasive or opinion writing - use op-ed-writer instead; for thesis-driven academic papers, use academic-essay; for verifying claims in existing text, use fact-checker.
whitepapers
Structures and writes authoritative B2B whitepapers - a standalone executive summary, a data-sized problem statement, methodology, evidence-driven findings, recommendations, and a soft call to action - flagging every claim that needs a real citation. Use when a marketer asks "write a whitepaper on this topic", "turn our research into a lead-gen asset", "make this credible enough for executives", or a draft reads like a product pitch instead of analysis. Do NOT use for customer-story-driven proof - use case-study-builder instead; for policy audiences, use policy-brief.
white-space-analysis
Finds unmet market gaps by ranking jobs-to-be-done on importance versus satisfaction, overlaying them against current solutions and workarounds, and validating that each gap is real, winnable, and worth winning. Use when someone asks "where are the gaps in this market", "what's underserved here", "where should we play next", or is hunting for product, feature, or market-entry opportunities. Do NOT use for profiling competitors and building battle cards - use competitive-intelligence instead; for putting a dollar figure on an identified opportunity - use market-sizing instead; for extracting jobs-to-be-done from raw customer interviews - use jtbd-extractor instead.