rust-expert

安装量: 40
排名: #17882

安装

npx skills add https://github.com/oimiragieo/agent-studio --skill rust-expert

Rust Expert Apply idiomatic Rust patterns with strong safety, performance, and maintainability guarantees. Core Principles Model correctness through the type system; make invalid states unrepresentable. Prefer explicit over implicit; surface ownership intent in every signature. Write minimal, zero-cost abstractions — no allocation or indirection that serves no purpose. Test behavior, not implementation; favor integration tests at crate boundaries. 1. Ownership, Borrowing, and Lifetimes Ownership Patterns // Prefer moves when value is consumed; prefer borrows when value is shared. fn process ( data : Vec < u8

) -> Vec < u8

{ / consumes / } fn inspect ( data : & [ u8 ] ) -> usize { data . len ( ) / borrows / } // Clone only when necessary and explicitly documented. // Anti-pattern: clone() to silence borrow checker errors — fix the design instead. Borrowing Rules (enforce at design time) Only one mutable reference OR any number of immutable references at a time. References must not outlive the value they point to. Use Cow<'a, T> when you need borrow-or-own semantics without forcing allocation. use std :: borrow :: Cow ; fn normalize ( s : & str ) -> Cow < str

{ if s . contains ( ' ' ) { Cow :: Owned ( s . replace ( ' ' , "_" ) ) } else { Cow :: Borrowed ( s ) } } Lifetime Patterns // Explicit lifetime annotation: only when compiler cannot infer. struct Parser < 'input

{ source : & 'input str , pos : usize , } impl < 'input

Parser < 'input

{ fn next_token ( & mut self ) -> & 'input str { // Returns a slice of the original input — lifetime ties result to source. & self . source [ self . pos .. ] } } // RPIT (Return Position Impl Trait) avoids lifetime noise in many cases. fn words ( s : & str ) -> impl Iterator < Item = & str

{ s . split_whitespace ( ) } Anti-Patterns to Avoid clone() inside hot loops to avoid borrow checker. unsafe to bypass lifetime checks — redesign instead. 'static bounds that force heap allocation when borrowing suffices. Storing &mut T in structs — prefer owned data or RefCell . 2. Error Handling Decision Matrix Scenario Tool Library crate errors thiserror — typed, composable Application / binary errors anyhow — ergonomic ? chaining Domain-specific context Custom enum via thiserror Infallible conversions From / Into — zero overhead thiserror (Library Crates) use thiserror :: Error ;

[derive(Debug, Error)]

pub enum ConfigError {

[error(

"missing required field: {field}" )] MissingField { field : & 'static str } ,

[error(

"invalid value for {field}: {source}" )] ParseError { field : & 'static str ,

[source]

source : std :: num :: ParseIntError } ,

[error(transparent)]

Io (

[from]

std :: io :: Error ) , } anyhow (Application / Binary) use anyhow :: { Context , Result } ; fn load_config ( path : & str ) -> Result < Config

{ let raw = std :: fs :: read_to_string ( path ) . with_context ( | | format! ( "reading config from {path}" ) ) ? ; toml :: from_str ( & raw ) . context ( "parsing config TOML" ) } Error Propagation Rules Never use .unwrap() in library code; use .expect() only in tests and main() where the invariant is self-evident. Add .context() / .with_context() at every error boundary to preserve the call chain. Map errors at crate boundaries — do not leak internal error types in public APIs. 3. Trait System Generics vs Trait Objects // Prefer generics (monomorphized, zero-cost) when types are known at compile time. fn serialize < S : Serialize

( value : & S ) -> Vec < u8

{ / ... / } // Use dyn Trait only when you need heterogeneous collections or runtime dispatch. fn handlers ( ) -> Vec < Box < dyn EventHandler

{ / ... / } Where Clauses // Prefer where clauses for readability when bounds are complex. fn merge < K , V

( a : HashMap < K , V

, b : HashMap < K , V

) -> HashMap < K , V

where K : Eq + Hash , V : Clone , { / ... / } Blanket Implementations and Orphan Rules Implement standard traits ( Display , From , Iterator ) for your types. Respect the orphan rule: you may only implement a foreign trait for a local type. Use the newtype pattern to work around orphan restrictions. struct Wrapper ( Vec < u8

) ; impl fmt :: Display for Wrapper { fn fmt ( & self , f : & mut fmt :: Formatter < '_

) -> fmt :: Result { write! ( f , "{:?}" , self . 0 ) } } Rust 1.75+ Async in Traits (RPITIT) // Stable as of Rust 1.75 — no more async-trait macro needed. trait DataSource { async fn fetch ( & self , id : u64 ) -> anyhow :: Result < Record

; } // Return-Position Impl Trait in Traits (RPITIT) — also stable in 1.75. trait Transformer { fn transform ( & self , input : & str ) -> impl Iterator < Item = String

; } 4. Async / Await with Tokio Tokio Runtime Setup

[tokio::main]

async fn main ( ) -> anyhow :: Result < ( )

{ // Default: multi-thread runtime (all CPU cores). Ok ( ( ) ) } // Single-thread runtime for I/O-only workloads.

[tokio::main(flavor =

"current_thread" )] async fn main ( ) -> anyhow :: Result < ( )

{ Ok ( ( ) ) } Task Spawning use tokio :: task ; // Spawn a non-blocking async task. let handle = task :: spawn ( async move { fetch_data ( url ) . await } ) ; let result = handle . await ? ; // propagate JoinError // CPU-bound work must go to the blocking thread pool — never block the async runtime. let result = task :: spawn_blocking ( | | { expensive_cpu_computation ( ) } ) . await ? ; Channels use tokio :: sync :: { mpsc , oneshot , broadcast , watch } ; // mpsc: producer → consumer pipeline. let ( tx , mut rx ) = mpsc :: channel :: < Bytes

( 1024 ) ; // oneshot: request / response pattern. let ( resp_tx , resp_rx ) = oneshot :: channel :: < Result < Record

( ) ; // broadcast: fan-out to multiple subscribers. let ( tx , _rx ) = broadcast :: channel :: < Event

( 16 ) ; // watch: latest-value semantics (config reload, health state). let ( tx , rx ) = watch :: channel ( Config :: default ( ) ) ; select! and Cancellation use tokio :: select ; use tokio_util :: sync :: CancellationToken ; async fn worker ( token : CancellationToken ) { select! { _ = token . cancelled ( ) => { // Structured cancellation — always handle shutdown path. } result = do_work ( ) => { // Normal completion. } } } Structured Concurrency use tokio :: task :: JoinSet ; async fn fetch_all ( urls : Vec < String

) -> Vec < anyhow :: Result < Bytes

{ let mut set = JoinSet :: new ( ) ; for url in urls { set . spawn ( fetch ( url ) ) ; } let mut results = Vec :: new ( ) ; while let Some ( res ) = set . join_next ( ) . await { results . push ( res . expect ( "task panicked" ) ) ; } results } Async Anti-Patterns std::thread::sleep inside async context — use tokio::time::sleep . Holding MutexGuard across .await — use tokio::sync::Mutex instead. Blocking I/O (file read, DNS) on async thread — use spawn_blocking or tokio::fs . Unbounded channels — always set a capacity bound. 5. Common Crates serde — Serialization use serde :: { Deserialize , Serialize } ;

[derive(Debug, Serialize, Deserialize)]

[serde(rename_all =

"camelCase" )] pub struct User { pub id : u64 , pub display_name : String ,

[serde(skip_serializing_if =

"Option::is_none" )] pub email : Option < String

, } axum — HTTP Servers use axum :: { extract :: { Path , State } , routing :: get , Json , Router } ; async fn get_user ( State ( db ) : State < DbPool

, Path ( id ) : Path < u64

, ) -> Result < Json < User

, AppError

{ let user = db . find_user ( id ) . await ? ; Ok ( Json ( user ) ) } let app = Router :: new ( ) . route ( "/users/:id" , get ( get_user ) ) . with_state ( db_pool ) ; sqlx — Async Database use sqlx :: PgPool ; async fn insert_user ( pool : & PgPool , name : & str ) -> sqlx :: Result < i64

{ let row = sqlx :: query! ( "INSERT INTO users (name) VALUES ($1) RETURNING id" , name ) . fetch_one ( pool ) . await ? ; Ok ( row . id ) } reqwest — HTTP Client use reqwest :: Client ; async fn fetch_json < T : for < 'de

serde :: Deserialize < 'de

( client : & Client , url : & str , ) -> anyhow :: Result < T

{ Ok ( client . get ( url ) . send ( ) . await ? . error_for_status ( ) ? . json ( ) . await ? ) } rayon — Data Parallelism use rayon :: prelude :: * ; fn parallel_sum ( data : & [ f64 ] ) -> f64 { data . par_iter ( ) . sum ( ) } clap — CLI use clap :: Parser ;

[derive(Parser, Debug)]

[command(version, about)]

struct Args {

[arg(short, long, default_value =

"8080" )] port : u16 ,

[arg(short, long, env =

"CONFIG_PATH" )] config : std :: path :: PathBuf , } 6. Performance Optimization Zero-Cost Abstractions Prefer iterators over manual index loops — they compile to identical machine code. Use

[inline]

for hot, small functions that cross crate boundaries. Prefer stack allocation; move to heap ( Box , Vec , Arc ) only when necessary. SIMD // Use std::simd (nightly) or portable-simd crate for explicit vectorization. // Profile first — LLVM auto-vectorizes most iterator chains. use std :: simd :: { f32x8 , SimdFloat } ; fn dot_product_simd ( a : & [ f32 ] , b : & [ f32 ] ) -> f32 { a . chunks_exact ( 8 ) . zip ( b . chunks_exact ( 8 ) ) . map ( | ( a_chunk , b_chunk ) | { let va = f32x8 :: from_slice ( a_chunk ) ; let vb = f32x8 :: from_slice ( b_chunk ) ; ( va * vb ) . reduce_sum ( ) } ) . sum ( ) } Profiling

flamegraph (install: cargo install flamegraph)

cargo flamegraph --bin my-app

perf stat for CPU counters (Linux)

perf stat cargo run --release

heaptrack for heap allocation analysis (Linux)

heaptrack cargo run --release

criterion for micro-benchmarks

cargo bench Allocation Awareness // Preallocate when size is known. let mut v = Vec :: with_capacity ( expected_len ) ; // String building: use write! into a pre-allocated String. use std :: fmt :: Write ; let mut s = String :: with_capacity ( 256 ) ; write! ( s , "id={}" , id ) ? ; // Avoid format!() in hot paths — prefer direct write!() or push_str(). 7. Testing Unit Tests

[cfg(test)]

mod tests { use super :: * ;

[test]

fn parse_valid_config ( ) { let cfg = Config :: from_str ( "[server]\nport = 9000" ) . unwrap ( ) ; assert_eq! ( cfg . port , 9000 ) ; }

[test]

fn parse_invalid_port_returns_error ( ) { let result = Config :: from_str ( "[server]\nport = -1" ) ; assert! ( result . is_err ( ) ) ; } } Integration Tests // tests/integration/server.rs — tests the full public API.

[tokio::test]

async fn health_endpoint_returns_200 ( ) { let addr = spawn_test_server ( ) . await ; let resp = reqwest :: get ( format! ( "http://{addr}/health" ) ) . await . unwrap ( ) ; assert_eq! ( resp . status ( ) , 200 ) ; } Property-Based Testing (proptest) use proptest :: prelude :: * ; proptest! {

[test]

fn encode_decode_roundtrip ( data : Vec < u8

) { let encoded = encode ( & data ) ; prop_assert_eq! ( decode ( & encoded ) . unwrap ( ) , data ) ; } } Async Tests

[tokio::test]

async fn fetch_returns_data ( ) { let mock = MockServer :: start ( ) . await ; Mock :: given ( method ( "GET" ) ) . respond_with ( ResponseTemplate :: new ( 200 ) ) . mount ( & mock ) . await ; let result = fetch ( & mock . uri ( ) ) . await ; assert! ( result . is_ok ( ) ) ; } Test Conventions Test one behavior per test function. Use descriptive names: . Mock only external I/O boundaries (HTTP, filesystem, database). Run cargo test -- --nocapture for diagnostic output during development. Run cargo nextest run for faster parallel test execution in CI. 8. Unsafe Rust When to Use FFI boundaries (calling C functions, exporting to C). Low-level memory mapping or SIMD intrinsics when safe abstractions cannot reach. Performance-critical code where the safe alternative has measurable overhead (proved by profiling). Guidelines /// # Safety /// /// - ptr must be non-null and properly aligned for T. /// - The memory at ptr must be valid for len elements of type T. /// - The caller must ensure no other mutable references to the memory exist. pub unsafe fn from_raw_parts < T

( ptr : * const T , len : usize ) -> & 'static [ T ] { std :: slice :: from_raw_parts ( ptr , len ) } Every unsafe block must have a // SAFETY: comment explaining why it is sound. Minimize the scope of unsafe — wrap in a safe abstraction immediately. Prefer unsafe fn over unsafe block inside a safe fn when the entire function is unsafe. Use Miri ( cargo +nightly miri test ) to detect undefined behavior in unsafe code. 9. FFI and Interop Exporting to C

[no_mangle]

pub extern "C" fn my_add ( a : i32 , b : i32 ) -> i32 { a + b } Calling C from Rust extern "C" { fn strlen ( s : * const std :: os :: raw :: c_char ) -> usize ; } fn rust_strlen ( s : & str ) -> usize { let cstr = std :: ffi :: CString :: new ( s ) . expect ( "no null bytes" ) ; // SAFETY: cstr is a valid, null-terminated C string. unsafe { strlen ( cstr . as_ptr ( ) ) } } cbindgen / bindgen Use cbindgen to generate C headers from Rust public API. Use bindgen to generate Rust bindings from C headers. Always run through CI to detect API drift. 10. Build System Cargo Workspaces

Cargo.toml (workspace root)

[ workspace ] members = [ "crates/core" , "crates/server" , "crates/cli" ] resolver = "2" [ workspace.dependencies ] tokio = { version = "1" , features = [ "full" ] } serde = { version = "1" , features = [ "derive" ] }

crates/core/Cargo.toml

[ dependencies ] tokio.workspace = true serde.workspace = true Feature Flags [ features ] default = [ "std" ] std = [ ] async = [ "dep:tokio" ] metrics = [ "dep:prometheus" ] [ dependencies ] tokio = { version = "1" , optional = true } prometheus = { version = "0.13" , optional = true }

[cfg(feature =

"metrics" )] fn record_latency ( ms : u64 ) { / ... / }

[cfg(not(feature =

"metrics" ))] fn record_latency ( _ms : u64 ) { } Build Scripts // build.rs — runs before crate compilation. fn main ( ) { // Rerun when proto files change. println! ( "cargo:rerun-if-changed=proto/" ) ; // Link a system library. println! ( "cargo:rustc-link-lib=ssl" ) ; } Useful Cargo Commands cargo build --release

optimized build

cargo clippy -- -D warnings

lint (treat warnings as errors)

cargo fmt --check

format check (CI)

cargo doc --no-deps --open

generate and open docs

cargo audit

check dependencies for CVEs

cargo deny check

license + advisory checks

cargo expand

show macro expansion

  1. Rust 1.75+ Features Async fn in Traits (Stable 1.75) // No longer requires #[async_trait] crate. trait Fetcher { async fn fetch ( & self , url : & str ) -> anyhow :: Result < Bytes

    ; } RPITIT — Return-Position Impl Trait in Traits (Stable 1.75) trait Source { fn items ( & self ) -> impl Iterator < Item = & str

    ; } let-else (Stable 1.65) let Ok ( value ) = parse_value ( raw ) else { return Err ( ConfigError :: InvalidValue ) ; } ; std::io::ErrorKind — richer variants (ongoing) use std :: io :: ErrorKind ; match err . kind ( ) { ErrorKind :: NotFound => { / ... / } ErrorKind :: PermissionDenied => { / ... / } _ => { / ... / } }

  2. Code Quality Checklist Before marking Rust work complete: cargo clippy -- -D warnings passes with zero warnings. cargo fmt --check produces no changes. cargo test (or cargo nextest run ) passes — all tests green. All unsafe blocks have // SAFETY: comments. Public API items have /// doc comments. No .unwrap() in library code except tests. Error types derive Debug
  3. implement std::error::Error . Async code does not block the executor thread. Performance-critical paths benchmarked before and after changes. Assigned Agents This skill is used by: developer — Rust feature implementation and bug fixes code-reviewer — Rust-specific code review patterns qa — Rust testing strategies (proptest, criterion, nextest) Memory Protocol (MANDATORY) Before starting: Read .claude/context/memory/learnings.md After completing: New Rust pattern discovered -> .claude/context/memory/learnings.md Rust-specific issue or gotcha -> .claude/context/memory/issues.md Architecture or crate selection decision -> .claude/context/memory/decisions.md ASSUME INTERRUPTION: Your context may reset. If it's not in memory, it didn't happen.
返回排行榜