AgentSkillsCN

rust-pro

专业的Rust工程实践——所有权决策、错误处理、不安全代码审计与异步编程模式。

SKILL.md
--- frontmatter
name: rust-pro
description: Professional Rust engineering — ownership decisions, error handling, unsafe auditing, and async patterns

What I do

  • Guide ownership and borrowing decisions with a deterministic decision tree
  • Provide error handling protocols for libraries, applications, and CLIs
  • Define an unsafe audit protocol for verifying memory safety invariants
  • Cover async programming patterns, concurrency selection, and essential tooling

When to use me

Use this skill when writing, reviewing, or optimizing Rust code. It covers ownership decisions, error handling, trait design, unsafe auditing, and async patterns. Pair with perf-core for profiling methodology and benchmarking workflow.

Ownership Decision Tree

code
Need to own the data?
  NO  --> &T (shared borrow) or &mut T (exclusive borrow)
  YES |
      v
Need heap allocation? (large, recursive, or trait object)
  YES --> Box<T>
  NO  |
      v
Need shared ownership?
  NO  --> T (move semantics)
  YES |
      v
Shared across threads?
  YES --> Arc<T> (read-only) or Arc<Mutex<T>> (read-write)
  NO  --> Rc<T> (read-only) or Rc<RefCell<T>> (read-write)

Sometimes owned, sometimes borrowed?
  --> Cow<'_, T>

Every type has a drop story. Before creating a type that holds resources (file handles, connections, locks), decide what happens when it is dropped. If cleanup is needed, implement Drop.

  • Clone is correct for: small Copy types, prototyping, when ownership semantics require it
  • Clone is a smell when: silencing the borrow checker, in hot loops, on large allocations
  • Prefer: &T, Cow<'_, T>, or restructured ownership over reflexive cloning

Error Handling Protocol

ContextError TypeCratePattern
Library (public API)Custom enumthiserror#[derive(Error, Debug)] with #[error("...")]
ApplicationContextualanyhowanyhow::Result<T> with .context("...")
CLI toolContextual + exitanyhowanyhow::Result<()> in main()
Internal moduleCustom or anyhowEitherMatch module's public/private boundary
  1. Never .unwrap() in library code -- use ? or return Result
  2. Never .unwrap() in async code -- panics in tasks are hard to debug
  3. .unwrap() is acceptable in: tests, provably infallible cases (with comment), and early prototyping
  4. Prefer .expect("reason") over .unwrap() when unwrapping is justified
  5. Use map_err to convert between error types at module boundaries
  6. Preserve error chains -- do not discard inner errors

Lifetime Complexity Budget

LevelCostWhat It Looks LikeWhen To Use
1FreeElision (no annotation)Default for functions and methods
2Cheap'staticOwned data, static strings, thread spawning
3ModerateSingle 'aStructs borrowing one source, iterators
4ExpensiveMultiple 'a, 'bStructs borrowing multiple sources
5Very expensivePin<&mut Self>, PhantomData<&'a T>Self-referential types, async futures
  • Always try the cheaper level first. Escalate only when the compiler requires it.
  • If you are fighting the borrow checker at level 3+, restructure your data model to use owned data or Arc.
  • Most code stays at level 1-2. Level 4-5 should be rare and always documented.

Trait Design Patterns

DecisionChoose AChoose B
Open vs closed set of typesTrait (extensible by downstream)Enum (fixed set, exhaustive match)
One impl per type vs manyAssociated type (type Output)Generic parameter (<T>)
Need type-erased collectiondyn Trait (dynamic dispatch)Generic <T: Trait> (monomorphized)
Add methods to foreign typeExtension trait (trait FooExt)Newtype wrapper
Enforce compile-time propertyMarker trait (trait Sealed {})PhantomData or type state
  • Default to static dispatch (generics). Use dyn Trait only when you need heterogeneous collections or to reduce compile times.
  • Use supertraits to compose behavior: trait Service: Send + Sync + 'static.
  • Implement From<T> instead of custom conversion methods for type conversions.

Unsafe Audit Protocol

Valid reasons for unsafe:

  1. FFI -- calling C libraries or exposing Rust to C
  2. Performance primitive -- implementing a safe abstraction over raw pointers (e.g., custom collection)
  3. Compiler limitation -- working around borrow checker where you can prove safety (rare, document extensively)

Audit checklist:

  • Safety invariant documented in // SAFETY: comment above every unsafe block
  • cargo +nightly miri test passes for all tests exercising unsafe code
  • Unsafe code encapsulated behind a safe public API (no unsafe in public interface)
  • Adversarial inputs tested (null pointers, out-of-bounds, concurrent access)
  • Code review explicitly acknowledges unsafe and verifies invariant

Unsafe internals, safe externals. Every unsafe block should be wrapped in a function or module that exposes a safe API. Users of your code should never need to write unsafe.

Async & Concurrency

Async Patterns

PatternGuidance
Runtime selectionUse tokio unless you have a specific reason not to
Send + SyncAll data held across .await must be Send. Use Arc instead of Rc in async.
Locks across .awaitNever hold MutexGuard across an .await point. Drop the guard first or use tokio::sync::Mutex.
spawn vs spawn_blockingspawn for async work. spawn_blocking for CPU-bound or blocking I/O.
Cancellation safetyAssume any .await can be cancelled. Do not rely on destructors running after cancellation.
select!Use tokio::select! for racing futures. Every branch must be cancellation-safe.
StreamsPrefer StreamExt combinators over manual poll_next.

Concurrency Selection Matrix

NeedMechanismWhen
Exclusive mutable accessMutex<T>Short critical sections, low contention
Read-heavy shared accessRwLock<T>Many readers, rare writers
Message passingmpsc, oneshot, broadcastDecoupled components, actor pattern
Lock-free counter/flagAtomicUsize, AtomicBoolSimple shared state, high contention
Data parallelismrayonCPU-bound work over collections

Prefer message passing (channels) over shared state (Mutex). Channels make ownership transfer explicit and avoid deadlocks.

Tooling

ToolPurposeCommand
cargo clippyLint with pedantic rulescargo clippy -- -W clippy::pedantic
cargo fmtFormat code (rustfmt)cargo fmt --check (CI) / cargo fmt (fix)
cargo nextestFast parallel test runnercargo nextest run
cargo denyAudit deps for vulnerabilities and licensescargo deny check
cargo expandShow macro expansion outputcargo expand --lib
cargo miriDetect undefined behavior in unsafe codecargo +nightly miri test
cargo fuzzFuzz testing for crash discoverycargo fuzz run <target>
cargo docGenerate and verify documentationcargo doc --no-deps --document-private-items
baconBackground code checker (watch mode)bacon clippy

Run clippy, fmt --check, nextest, and deny check in CI on every pull request. Add miri for crates with unsafe code.

Anti-Patterns

Anti-PatternWhy It Fails
.clone() to silence the borrow checkerHides ownership design flaws; creates unnecessary allocations in hot paths
Arc<Mutex<T>> as default shared stateAdds overhead when single-threaded Rc<RefCell<T>> or channels suffice
.unwrap() in library codePanics are unrecoverable; callers cannot handle the error
String parameters instead of &strForces callers to allocate; accept impl AsRef<str> or &str
Box<dyn Error> in public library APICallers cannot match on error variants; use thiserror enum
Holding MutexGuard across .awaitBlocks the executor thread; causes deadlocks with single-threaded runtime
Fighting the borrow checker with lifetimesRestructure data model to use owned data or Arc instead
unsafe without // SAFETY: commentInvariant is undocumented; cannot be verified during review
Ignoring clippy::pedanticMisses idiomatic patterns, potential bugs, and API design issues

Companion Skills

DomainSkillCoverage
Performance analysisperf-coreProfiling methodology, flame graphs, benchmarking workflow
Rust performanceperf-rustProfiling, benchmarking, codegen flags, zero-allocation patterns
Container buildscontainer-opsMulti-stage Dockerfile for Rust, image optimization