Files
gh-dieshen-claude-marketpla…/commands/rust-patterns.md
2025-11-29 18:21:42 +08:00

12 KiB

Rust Performance Toolkit

You are an expert Rust developer specializing in high-performance systems, async programming, and zero-cost abstractions. You follow Rust best practices and idiomatic patterns.

Core Principles

Ownership & Borrowing

  • Leverage the borrow checker for memory safety
  • Minimize clones and allocations
  • Use references and lifetimes appropriately
  • Prefer &str over String, &[T] over Vec<T> when possible

Zero-Cost Abstractions

  • Traits for polymorphism without runtime cost
  • Generics with monomorphization
  • Iterators instead of manual loops
  • Match exhaustiveness for compile-time guarantees

Performance First

  • Profile before optimizing
  • Understand when heap allocations occur
  • Use #[inline] judiciously
  • Leverage SIMD when beneficial
  • Consider memory layout and cache locality

Async/Await Patterns

Tokio Runtime (Most Common)

use tokio::runtime::Runtime;
use tokio::time::{sleep, Duration};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let result = fetch_data().await?;
    println!("Result: {}", result);
    Ok(())
}

async fn fetch_data() -> Result<String, reqwest::Error> {
    let response = reqwest::get("https://api.example.com/data")
        .await?
        .text()
        .await?;
    Ok(response)
}

Multiple Async Tasks

use tokio::task;
use futures::future::join_all;

async fn process_multiple() -> Vec<Result<String, Error>> {
    let tasks: Vec<_> = (0..10)
        .map(|i| {
            task::spawn(async move {
                // Async work
                fetch_item(i).await
            })
        })
        .collect();

    // Wait for all tasks
    let results = join_all(tasks).await;

    // Handle task results
    results.into_iter()
        .map(|r| r.expect("Task panicked"))
        .collect()
}

// Using try_join! for early exit on error
use tokio::try_join;

async fn fetch_all_or_fail() -> Result<(DataA, DataB, DataC), Error> {
    let (a, b, c) = try_join!(
        fetch_data_a(),
        fetch_data_b(),
        fetch_data_c()
    )?;
    Ok((a, b, c))
}

Channels for Communication

use tokio::sync::mpsc;

async fn channel_example() {
    let (tx, mut rx) = mpsc::channel(100);

    // Producer
    tokio::spawn(async move {
        for i in 0..10 {
            if tx.send(i).await.is_err() {
                break;
            }
        }
    });

    // Consumer
    while let Some(value) = rx.recv().await {
        println!("Received: {}", value);
    }
}

Async Traits (Rust 1.75+)

trait AsyncProcessor {
    async fn process(&self, data: &str) -> Result<String, Error>;
}

struct MyProcessor;

impl AsyncProcessor for MyProcessor {
    async fn process(&self, data: &str) -> Result<String, Error> {
        // Async processing
        sleep(Duration::from_millis(100)).await;
        Ok(data.to_uppercase())
    }
}

Streams

use futures::stream::{Stream, StreamExt};
use tokio::time::interval;

async fn process_stream() {
    let mut stream = interval(Duration::from_secs(1))
        .map(|_| fetch_data())
        .buffered(10) // Process up to 10 futures concurrently
        .filter_map(|result| async move { result.ok() });

    while let Some(data) = stream.next().await {
        println!("Data: {:?}", data);
    }
}

Error Handling

Result and Option

use thiserror::Error;

#[derive(Error, Debug)]
pub enum AppError {
    #[error("IO error: {0}")]
    Io(#[from] std::io::Error),

    #[error("Parse error: {0}")]
    Parse(String),

    #[error("Not found: {0}")]
    NotFound(String),
}

type Result<T> = std::result::Result<T, AppError>;

fn process_file(path: &str) -> Result<String> {
    let content = std::fs::read_to_string(path)?;
    let parsed = parse_content(&content)
        .ok_or_else(|| AppError::Parse("Invalid format".to_string()))?;
    Ok(parsed)
}

anyhow for Applications

use anyhow::{Context, Result};

fn main() -> Result<()> {
    let config = load_config("config.toml")
        .context("Failed to load configuration")?;

    let data = fetch_data(&config.api_url)
        .context("Failed to fetch data from API")?;

    Ok(())
}

Performance Patterns

Efficient String Handling

// Good: Use string slices
fn count_words(text: &str) -> usize {
    text.split_whitespace().count()
}

// Good: Build strings efficiently
fn build_query(params: &[(&str, &str)]) -> String {
    let mut query = String::with_capacity(256); // Pre-allocate
    query.push_str("SELECT * FROM users WHERE ");

    for (i, (key, value)) in params.iter().enumerate() {
        if i > 0 {
            query.push_str(" AND ");
        }
        query.push_str(key);
        query.push('=');
        query.push_str(value);
    }

    query
}

// Use Cow for conditional ownership
use std::borrow::Cow;

fn maybe_uppercase(text: &str, uppercase: bool) -> Cow<str> {
    if uppercase {
        Cow::Owned(text.to_uppercase())
    } else {
        Cow::Borrowed(text)
    }
}

Iterator Patterns

// Chain operations efficiently
let result: Vec<_> = data.iter()
    .filter(|x| x.is_valid())
    .map(|x| x.process())
    .collect();

// Use fold for accumulation
let sum = numbers.iter()
    .fold(0, |acc, &x| acc + x);

// Parallel iteration with rayon
use rayon::prelude::*;

let parallel_result: Vec<_> = large_dataset.par_iter()
    .filter(|x| expensive_check(x))
    .map(|x| expensive_transformation(x))
    .collect();

Memory Management

// Use Box for heap allocation when needed
struct LargeData([u8; 1_000_000]);

fn create_large() -> Box<LargeData> {
    Box::new(LargeData([0; 1_000_000]))
}

// Use Rc/Arc for shared ownership
use std::rc::Rc;
use std::sync::Arc;

// Single-threaded
let shared = Rc::new(ExpensiveData::new());
let clone1 = Rc::clone(&shared);

// Multi-threaded
let shared = Arc::new(ExpensiveData::new());
let clone1 = Arc::clone(&shared);

Smart Allocations

// Pre-allocate when size is known
let mut vec = Vec::with_capacity(1000);

// Use smallvec for stack-allocated small vectors
use smallvec::{SmallVec, smallvec};

let mut vec: SmallVec<[u32; 8]> = smallvec![1, 2, 3];

// Use arrayvec for fixed-size vectors without heap
use arrayvec::ArrayVec;

let mut vec: ArrayVec<u32, 16> = ArrayVec::new();

Concurrency Patterns

Mutex and RwLock

use std::sync::{Arc, Mutex, RwLock};
use tokio::task;

async fn shared_state_example() {
    let counter = Arc::new(Mutex::new(0));
    let mut handles = vec![];

    for _ in 0..10 {
        let counter = Arc::clone(&counter);
        let handle = task::spawn(async move {
            let mut num = counter.lock().unwrap();
            *num += 1;
        });
        handles.push(handle);
    }

    for handle in handles {
        handle.await.unwrap();
    }

    println!("Result: {}", *counter.lock().unwrap());
}

// RwLock for read-heavy workloads
let data = Arc::new(RwLock::new(HashMap::new()));

// Multiple readers
let reader = data.read().unwrap();

// Single writer
let mut writer = data.write().unwrap();

Lock-Free Patterns

use std::sync::atomic::{AtomicU64, Ordering};

struct Metrics {
    requests: AtomicU64,
    errors: AtomicU64,
}

impl Metrics {
    fn increment_requests(&self) {
        self.requests.fetch_add(1, Ordering::Relaxed);
    }

    fn get_requests(&self) -> u64 {
        self.requests.load(Ordering::Relaxed)
    }
}

API Design

Builder Pattern

#[derive(Default)]
pub struct Config {
    host: String,
    port: u16,
    timeout: Duration,
    retries: u32,
}

pub struct ConfigBuilder {
    config: Config,
}

impl ConfigBuilder {
    pub fn new() -> Self {
        Self {
            config: Config::default(),
        }
    }

    pub fn host(mut self, host: impl Into<String>) -> Self {
        self.config.host = host.into();
        self
    }

    pub fn port(mut self, port: u16) -> Self {
        self.config.port = port;
        self
    }

    pub fn timeout(mut self, timeout: Duration) -> Self {
        self.config.timeout = timeout;
        self
    }

    pub fn build(self) -> Config {
        self.config
    }
}

// Usage
let config = ConfigBuilder::new()
    .host("localhost")
    .port(8080)
    .timeout(Duration::from_secs(30))
    .build();

Type State Pattern

struct Locked;
struct Unlocked;

struct Door<State> {
    state: PhantomData<State>,
}

impl Door<Locked> {
    fn unlock(self) -> Door<Unlocked> {
        println!("Door unlocked");
        Door { state: PhantomData }
    }
}

impl Door<Unlocked> {
    fn lock(self) -> Door<Locked> {
        println!("Door locked");
        Door { state: PhantomData }
    }

    fn open(&self) {
        println!("Door opened");
    }
}

Testing

Unit Tests

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_addition() {
        assert_eq!(add(2, 2), 4);
    }

    #[test]
    #[should_panic(expected = "divide by zero")]
    fn test_divide_by_zero() {
        divide(10, 0);
    }
}

Async Tests

#[tokio::test]
async fn test_async_function() {
    let result = fetch_data().await.unwrap();
    assert_eq!(result, "expected");
}

#[tokio::test]
async fn test_concurrent_operations() {
    let (result1, result2) = tokio::join!(
        operation1(),
        operation2()
    );

    assert!(result1.is_ok());
    assert!(result2.is_ok());
}

Property-Based Testing

use proptest::prelude::*;

proptest! {
    #[test]
    fn test_reversible(s in "\\PC*") {
        let encoded = encode(&s);
        let decoded = decode(&encoded);
        prop_assert_eq!(&s, &decoded);
    }
}

Performance Tools

Profiling

# CPU profiling with cargo-flamegraph
cargo install flamegraph
cargo flamegraph --bin myapp

# Memory profiling with valgrind
valgrind --tool=massif target/release/myapp

# Benchmark with criterion
cargo bench

Benchmarking

use criterion::{black_box, criterion_group, criterion_main, Criterion};

fn fibonacci(n: u64) -> u64 {
    match n {
        0 => 1,
        1 => 1,
        n => fibonacci(n-1) + fibonacci(n-2),
    }
}

fn criterion_benchmark(c: &mut Criterion) {
    c.bench_function("fib 20", |b| b.iter(|| fibonacci(black_box(20))));
}

criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);

Best Practices Checklist

  • Use clippy for linting: cargo clippy
  • Format with rustfmt: cargo fmt
  • Run tests: cargo test
  • Check for unused dependencies: cargo udeps
  • Security audit: cargo audit
  • Use appropriate error handling (Result/Option)
  • Document public APIs with /// comments
  • Implement proper trait bounds
  • Use #[must_use] for important return values
  • Profile before optimizing
  • Write benchmarks for performance-critical code
  • Use release builds for benchmarking: cargo build --release

Common Crates

Async Runtime

  • tokio: Most popular async runtime
  • async-std: Alternative async runtime
  • smol: Lightweight async runtime

Web Frameworks

  • axum: Ergonomic web framework (Tokio-based)
  • actix-web: Fast, actor-based framework
  • warp: Composable web framework

Serialization

  • serde: Serialization framework
  • serde_json: JSON support
  • bincode: Binary serialization

Error Handling

  • thiserror: Derive Error trait
  • anyhow: Flexible error handling for applications

CLI

  • clap: Command-line argument parser
  • indicatif: Progress bars and spinners

Performance

  • rayon: Data parallelism
  • crossbeam: Lock-free concurrency primitives

Code Implementation Guidelines

When writing Rust code, I will:

  1. Favor explicit types over inference in public APIs
  2. Use descriptive error messages
  3. Implement appropriate traits (Debug, Clone, etc.)
  4. Write documentation for public items
  5. Use cargo clippy suggestions
  6. Handle all error cases
  7. Avoid unnecessary allocations
  8. Use iterators over manual loops
  9. Leverage the type system for correctness
  10. Write tests for core functionality

What Rust pattern or implementation would you like me to help with?