Rust Expert
Fight the design, not the borrow checker — a borrow error is usually a real aliasing/lifetime problem. Make invalid states unrepresentable, return errors as values, and reach for clone()/unsafe deliberately, never to silence the compiler.
When to Use
- Writing or refactoring Rust.
- Borrow-checker, lifetime, move/
clone, or Send/Sync errors.
- Designing traits, generics, error types, or APIs.
- Async (tokio), iterators, smart pointers, or vetting
unsafe.
When NOT to Use
- Other languages → the relevant language skill.
- High-level system architecture →
software-architect.
Core Principles
1. Ownership & borrowing by design
- Borrow (
&T/&mut T) over cloning; clone() is a real cost you choose, not an error silencer.
- The rule: many
&T xor one &mut T. When it fights you, restructure: narrow scopes, split borrows, index-based access, or interior mutability (Cell/RefCell single-thread, Mutex/RwLock multi-thread).
- Params take
&str/&[T]/impl AsRef<_>; store owned String/Vec<T>. Return owned data or borrow tied to an input lifetime.
2. Errors are values
- Return
Result<T, E>; propagate with ?. Avoid unwrap()/expect() outside tests, main, or provably-infallible spots (and then expect() with a reason).
- Libraries: typed errors with
thiserror (impl std::error::Error, use #[from]). Apps: anyhow::Result with .context("…").
Option for absence, Result for failure. Don't panic on recoverable conditions.
3. Model with the type system
- Enums to make illegal states unrepresentable; newtype pattern for domain values and to add trait impls.
- Generics + trait bounds by default;
dyn Trait (boxed) for heterogeneous collections, plugin points, or smaller code size. Derive Debug/Clone/PartialEq where sensible; impl From for conversions so ? composes.
4. Async & concurrency
- Pick one runtime (usually
tokio). Never block the executor — use async I/O; offload CPU/blocking work to spawn_blocking or a thread pool.
- Share state with
Arc<Mutex<T>>/Arc<RwLock<T>>; hold locks for the shortest possible scope and never across .await. Move ownership across tasks via channels.
5. unsafe is a contract
- Only for proven needs (FFI, niche perf). Wrap it in a safe API, document the invariants you uphold, and keep
unsafe blocks minimal. Run Miri/sanitizers.
Common Mistakes
.clone() / .to_owned() spam to dodge borrow errors → restructure ownership instead.
.unwrap() in library/production code → propagate with ? or handle.
- Self-referential structs / returning refs to locals → return owned data or rethink lifetimes; reach for an index or
Rc/Arc.
- Holding a
MutexGuard across .await → deadlocks/!Send futures; drop the guard first.
- Over-annotating lifetimes → most are elided; add them only when the compiler asks.
Vec<Box<dyn Trait>> when generics suffice → unnecessary dynamic dispatch.
Examples
Idiomatic library error type
use thiserror::Error;
#[derive(Error, Debug)]
pub enum ConfigError {
#[error("missing field: {0}")]
Missing(String),
#[error(transparent)]
Io(#[from] std::io::Error), // `?` converts io::Error automatically
}
pub fn load(path: &str) -> Result<String, ConfigError> {
let text = std::fs::read_to_string(path)?;
if text.trim().is_empty() {
return Err(ConfigError::Missing("body".into()));
}
Ok(text)
}
Make invalid states unrepresentable
enum Connection {
Disconnected,
Connecting { attempt: u8 },
Connected { session: String }, // a session ONLY exists when connected
}
See Also
performance-expert — profiling, allocation reduction, flamegraphs.
testing-expert — #[test], integration tests, proptest.
cpp-expert — comparing systems-language trade-offs.
go-expert — different concurrency model for the same problem space.
Source: Miaoge-Ge/coding-agent-skills — distributed by TomeVault.
1---2name: miaoge-ge-coding-agent-skills-rust-expert3description: Rust Expert4---56# Rust Expert78> Fight the design, not the borrow checker — a borrow error is usually a real aliasing/lifetime problem. Make invalid states unrepresentable, return errors as values, and reach for `clone()`/`unsafe` deliberately, never to silence the compiler.910## When to Use11- Writing or refactoring Rust.12- Borrow-checker, lifetime, move/`clone`, or `Send`/`Sync` errors.13- Designing traits, generics, error types, or APIs.14- Async (tokio), iterators, smart pointers, or vetting `unsafe`.1516## When NOT to Use17- Other languages → the relevant language skill.18- High-level system architecture → `software-architect`.1920## Core Principles2122### 1. Ownership & borrowing by design23- Borrow (`&T`/`&mut T`) over cloning; `clone()` is a real cost you choose, not an error silencer.24- The rule: many `&T` **xor** one `&mut T`. When it fights you, restructure: narrow scopes, split borrows, index-based access, or interior mutability (`Cell`/`RefCell` single-thread, `Mutex`/`RwLock` multi-thread).25- Params take `&str`/`&[T]`/`impl AsRef<_>`; store owned `String`/`Vec<T>`. Return owned data or borrow tied to an input lifetime.2627### 2. Errors are values28- Return `Result<T, E>`; propagate with `?`. Avoid `unwrap()`/`expect()` outside tests, `main`, or provably-infallible spots (and then `expect()` with a reason).29- **Libraries**: typed errors with `thiserror` (impl `std::error::Error`, use `#[from]`). **Apps**: `anyhow::Result` with `.context("…")`.30- `Option` for absence, `Result` for failure. Don't panic on recoverable conditions.3132### 3. Model with the type system33- Enums to make illegal states unrepresentable; newtype pattern for domain values and to add trait impls.34- Generics + trait bounds by default; `dyn Trait` (boxed) for heterogeneous collections, plugin points, or smaller code size. Derive `Debug`/`Clone`/`PartialEq` where sensible; impl `From` for conversions so `?` composes.3536### 4. Async & concurrency37- Pick one runtime (usually `tokio`). **Never block the executor** — use async I/O; offload CPU/blocking work to `spawn_blocking` or a thread pool.38- Share state with `Arc<Mutex<T>>`/`Arc<RwLock<T>>`; hold locks for the shortest possible scope and never across `.await`. Move ownership across tasks via channels.3940### 5. `unsafe` is a contract41- Only for proven needs (FFI, niche perf). Wrap it in a safe API, document the invariants you uphold, and keep `unsafe` blocks minimal. Run Miri/sanitizers.4243## Common Mistakes44- **`.clone()` / `.to_owned()` spam** to dodge borrow errors → restructure ownership instead.45- **`.unwrap()` in library/production code** → propagate with `?` or handle.46- **Self-referential structs / returning refs to locals** → return owned data or rethink lifetimes; reach for an index or `Rc`/`Arc`.47- **Holding a `MutexGuard` across `.await`** → deadlocks/`!Send` futures; drop the guard first.48- **Over-annotating lifetimes** → most are elided; add them only when the compiler asks.49- **`Vec<Box<dyn Trait>>` when generics suffice** → unnecessary dynamic dispatch.5051## Examples5253**Idiomatic library error type**54```rust55use thiserror::Error;5657#[derive(Error, Debug)]58pub enum ConfigError {59 #[error("missing field: {0}")]60 Missing(String),61 #[error(transparent)]62 Io(#[from] std::io::Error), // `?` converts io::Error automatically63}6465pub fn load(path: &str) -> Result<String, ConfigError> {66 let text = std::fs::read_to_string(path)?;67 if text.trim().is_empty() {68 return Err(ConfigError::Missing("body".into()));69 }70 Ok(text)71}72```7374**Make invalid states unrepresentable**75```rust76enum Connection {77 Disconnected,78 Connecting { attempt: u8 },79 Connected { session: String }, // a session ONLY exists when connected80}81```8283## See Also84- `performance-expert` — profiling, allocation reduction, flamegraphs.85- `testing-expert` — `#[test]`, integration tests, `proptest`.86- `cpp-expert` — comparing systems-language trade-offs.87- `go-expert` — different concurrency model for the same problem space.8889---90> Source: [Miaoge-Ge/coding-agent-skills](https://github.com/Miaoge-Ge/coding-agent-skills) — distributed by [TomeVault](https://tomevault.io).91<!-- tomevault:4.0:skill_md:2026-06-16 -->