rust-patterns
/rust-patterns
Pisze i recenzuje idiomatyczny Rust z regułami decyzyjnymi dla własności w sygnaturach API, unikaniem czasów życia, wyborem thiserror lub anyhow, stylem iteratorów oraz uzasadnionym użyciem Rc, RefCell, Arc i Mutex.
- Autor
- SkillMedev ↗
- Repozytorium
- skills ↗
- Licencja
- MIT
- Ostatnia zmiana repozytorium
- 13.10.2026
Ocena i bezpieczeństwo
- Pliki w paczce
- 1
- Skrypty
- 0
- Komentarze HTML
- 0
- Adresy URL w SKILL.md
- 0
Brak sygnałów. Repozytorium zmienione 13.10.2026, bez odwołań do wycofanych modeli i API.
MIT, otwarta: można używać i zmieniać, z zachowaniem informacji o autorze.
Co robi i kiedy
Pisze i recenzuje idiomatyczny Rust z regułami decyzyjnymi dla własności w sygnaturach API, unikaniem czasów życia, wyborem thiserror lub anyhow, stylem iteratorów oraz uzasadnionym użyciem Rc, RefCell, Arc i Mutex.
Wyzwalacze z opisu
Oryginalny opis (EN)
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 desig
Jak działa
- Operating procedure
- Worked artifact: signature and sharing, bad/good
- Deliverable
- Do NOT
- Quality bar
- Escalation
Podgląd SKILL.md
---
name: 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.
metadata:
title: "Rust Patterns"
---
# 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
1. Binary or library? Libraries need typed errors and conservative signatures; binaries can be looser.
2. Threading/async story: single-threaded, threaded, or async (which runtime - assume Tokio unless told otherwise).
3. Performance sensitivity: hot path or glue code. Label guesses as guesses; don't optimize glue.
4. 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:
1. Only reads? Take `&T` - and prefer the borrowed view type: `&str` over `&String`, `&[T]` over `&Vec<T>`, `&Path` over `&PathBuf`. This makes the API maximally callable.
2. Mutates in place? Take `&mut T`.
3. Stores or consumes the value (moves it into a struct, sends it to a channel, returns it)? Take `T` by value - taking `&T` and cloning inside hides the cost from the caller; taking `T` lets callers who are done with the value move it for free.
4. 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.
1. Plain ownership + borrows (the default, ~95% of code).
2. `Rc<T>` - single-threaded shared ownership (e.g. a graph node referenced by several parents).
3. `Rc<RefCell<T>>` - add interior mutability; runtime borrow panics replace compile errors, so keep borrow scopes tiny.
4. `Arc<T>` / `Arc<Mutex<T>>` (or `RwLock`) - cross-thread. In async code, never hold a `std::sync::Mutex` guard 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!`/`expect` only for invariant violations that indicate a bug, and `expect("why this cannot fail")` with a reason, never bare `unwrap()` on production paths.
```rust
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`/`Result` combinators (`map`, `and_then`, `ok_or`, `unwrap_or_else`) over nested `match`; but a `match` is still clearer than a four-combinator chain.
- Newtypes for domain values (`struct UserId(u64)`) so the compiler catches transposed arguments; implement `From` for conversions; derive `Debug, Clone, PartialEq` where sensible.
- Make illegal states unrepresentable: an enum with `Connected(Session)` / `Disconnected` beats a struct with `is_connected: bool` and a nullable session.
- Generics for hot-path polymorphism (monomorphized, inlined); `dyn Trait` for 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_capacity` when size is known; avoid collecting intermediates you immediately iterate again.
- Profile before optimizing anything beyond these defaults.
- `unsafe` stays tiny, wrapped in a safe API, with a `// SAFETY:` comment stating the invariant.
- `cargo clippy -- -D warnings` and `cargo fmt --check` run in CI, not just locally.
## Worked artifact: signature and sharing, bad/good
Bad - fights the checker, hides costs, panics:
```rust
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:
```rust
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>`/`&PathBuf` in signatures; the borrowed view types cost nothing and accept more callers.
- Do not reach for `Arc<Mutex<T>>` in single-threaded code, or `Rc<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; `?` or `expect("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_blocking` for it.
## Quality bar
- `cargo clippy -- -D warnings` clean; `cargo fmt` clean.
- No bare `unwrap()` outside tests; every `expect` states why it can't fail.
- Every `clone()` in the diff is annotated or eliminated.
- Public API takes borrowed view types and returns owned values.
- `unsafe` blocks 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.
Wersje
| Repozytorium | Wynik | Licencja |
|---|---|---|
| Ta strona · SkillMedev/skills | 100 | MIT |
| sutchan/Agent-Skills-Hub | 100 | MIT |
| j4flmao/agent-skills | 90 | MIT |
Migawka bazy z 28.10.2026 · wersja paczki d23a386eafd4 (skrót SHA-256 całej paczki)
Pobrania w ostatnich 30 dniach: 0 · ranking
Zgłoś problem z tym skillem: szkodliwa treść, błędny opis albo prośba autora o usunięcie.
Podobne skille
- 95ownership-not-clone
Sprawdza, czy klonowanie w Rust jest uzasadnione, i zamiast Rc, RefCell lub Arc Mutex proponuje własność i pożyczanie. - 100rust-expert
Pomaga w Rust przy własności, pożyczaniu, czasie życia, traitach, async i obsłudze błędów, także przy unsafe, Arc i Mutex. - 100rust
Pomaga pisać Rust i rozwiązywać błędy borrow checkera, czasów życia i cech: modele własności, thiserror i anyhow, async z Tokio oraz bezpieczne abstrakcje nad unsafe. - 95rust
Pomaga pisać, przeglądać, testować i wydawać kod Rust: własność i borrow checker, błędy z Result i thiserror, async na tokio, usługi axum 0.8, cargo test oraz sqlx z cargo-audit. - 90rust-error-handling
Projektuje obsługę błędów w Rust: thiserror a anyhow, typowane enumy błędów, konwersje #[from] oraz błędy odzyskiwalne a panic. - 100rust-conventions
Podpowiada konwencje Rust: własność i pożyczanie, Result i Option, operator ?, wybór thiserror lub anyhow oraz clippy i rustfmt w CI.