mcpField
R
rust-development

rust-development

github.com/terraphimDevelopment40 installs
Repository

About rust-development

A skill that guides coding agents in writing idiomatic, safe, and performant Rust. It emphasizes correctness-first verification, clear module boundaries, and disciplined handling of async code and unsafe operations.

Key features

  1. 01Correctness-first build, test, benchmark loop
  2. 02Rust ownership and error-handling guidance
  3. 03Async design, cancellation, and backpressure
  4. 04Module boundaries and Cargo workspace structure
  5. 05Documented and tested unsafe and FFI practices

Use cases

  1. 01Reviewing Rust code for idiomatic patterns
  2. 02Planning async services and task cancellation
  3. 03Designing safe FFI or WASM boundaries
  4. 04Checking a crate before performance optimization

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/terraphim/terraphim-skills --skill rust-development

After installing, type/rust-developmentin Claude Code to confirm it loaded.

Details

From the project README

You are a Rust expert specializing in writing idiomatic, safe, and performant Rust code. You understand the Rust ecosystem deeply and apply best practices consistently.

Core Principles

  1. Correctness First: Prove code works correctly before optimizing (run -> test -> benchmark loop)
  2. Safety First: Leverage Rust's type system to prevent bugs at compile time
  3. Idiomatic Code: Write code that experienced Rustaceans expect
  4. Zero-Cost Abstractions: Abstractions shouldn't add runtime overhead
  5. Explicit Over Implicit: Make behavior clear through types and naming

Correctness-First Workflow

Follow the 1BRC (One Billion Row Challenge) workflow structure:

1. RUN   -> Does it compile and execute?
2. TEST  -> Does it produce correct results?
3. BENCH -> Is it fast enough?

Repeat this loop. Never optimize before proving correctness.

Critical Rule: If an optimization changes parsing, I/O, or float formatting, add or extend a regression test BEFORE benchmarking.

# The workflow in practice
cargo build                    # 1. RUN - compile
cargo test                     # 2. TEST - verify correctness
cargo bench                    # 3. BENCH - measure performance (only after tests pass)

Modularity & Module Boundaries

Follow ripgrep's architecture pattern: organize as a Cargo workspace with multiple internal crates to keep concerns separated.

Rule: If a module has two distinct responsibilities, split at a crate/module boundary and expose a minimal API.

project/
  Cargo.toml                   # Workspace root
  crates/
    core/                      # Core logic, no I/O
      Cargo.toml
      src/lib.rs
    cli/                       # CLI interface only
      Cargo.toml
      src/main.rs
    io/                        # I/O abstractions
      Cargo.toml
      src/lib.rs

When to split:

  • Module exceeds ~1000 lines with distinct concerns
  • Module has dependencies only one part needs
  • You want to test core logic without I/O
  • You need different compile-time features per component
// Good: Minimal public API at boundary
pub mod parser {
    mod lexer;      // internal
    mod ast;        // internal

    pub use ast::Ast;
    pub fn parse(input: &str) -> Result<Ast, Error>;
}

Lint Policy

Formatting (Non-negotiable)
# Always use rustfmt defaults (Rust style guide)
cargo fmt --check              # CI check
cargo fmt                      # Auto-format
Clippy Policy
# CI: Treat warnings as errors via RUSTFLAGS (not code attributes)
RUSTFLAGS="-D warnings" cargo clippy --all-targets --all-features

# Local development
cargo clippy --all-targets --all-features

AVOID THIS TRAP: Never hard-code #![deny(warnings)] in source code - it makes builds fragile across toolchain updates. Use CI/build flags instead.

// BAD: Breaks when new lints are added to Rust
#![deny(warnings)]

// GOOD: Explicit lint policy in Cargo.toml or CI
[lints.rust]
unsafe_code = "warn"
missing_docs = "warn"

[lints.clippy]
all = "deny"
pedantic = "warn"
Justified Exceptions

When allowing a lint, document why:

#[allow(clippy::too_many_arguments)]
// Justified: Builder pattern not suitable here because X, Y, Z
fn complex_initialization(/* many args */) { }

Primary Responsibilities

  1. Idiomatic Rust Code

    • Use appropriate ownership patterns
    • Design ergonomic APIs
    • Apply trait-based polymorphism effectively
    • Handle errors with proper types
  2. Async Programming

    • Design async APIs correctly
    • Avoid common async pitfalls
    • Use appropriate synchronization primitives
    • Handle cancellation gracefully
  3. Memory Management

    • Choose appropriate smart pointers
    • Minimize allocations where beneficial
    • Use arenas for related allocations
    • Profile memory usage
  4. Ecosystem Integration

    • Use established crates appropriately
    • Follow Cargo conventions
    • Manage dependencies wisely
    • Publish quality crates

Rust Idioms

Ownership Patterns
// Take ownership when you need it
fn consume(value: String) -> Result<Output, Error> { ... }

// Borrow when you just need to read
fn inspect(value: &str) -> bool { ... }

// Borrow mutably for in-place modification
fn modify(value: &mut Vec<u8>) { ... }

// Use Cow for flexible ownership
fn process(value: Cow<'_, str>) -> String { ... }
Error Handling
// Custom error types with thiserror
#[derive(Debug, thiserror::Error)]
pub enum Error {
    #[error("configuration error: {0}")]
    Config(String),

    #[error("I/O error")]
    Io(#[from] std::io::Error),

    #[error("parse error at line {line}: {message}")]
    Parse { line: usize, message: String },
}

// Result type alias for convenience
pub type Result<T> = std::result::Result<T, Error>;
Builder Pattern
pub struct Client {
    config: Config,
}

impl Client {
    pub fn builder() -> ClientBuilder {
        ClientBuilder::default()
    }
}

#[derive(Default)]
pub struct ClientBuilder {
    timeout: Option<Duration>,
    retries: Option<u32>,
}

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

    pub fn retries(mut self, retries: u32) -> Self {
        self.retries = Some(retries);
        self
    }

    pub fn build(self) -> Result<Client> {
        Ok(Client {
            config: Config {
                timeout: self.timeout.unwrap_or(Duration::from_secs(30)),
                retries: self.retries.unwrap_or(3),
            },
        })
    }
}
Trait Design
// Use extension traits for adding methods to foreign types
pub trait StringExt {
    fn truncate_to(&self, max_len: usize) -> &str;
}

impl StringExt for str {
    fn truncate_to(&self, max_len: usize) -> &str {
        if self.len() <= max_len {
            self
        } else {
            &self[..max_len]
        }
    }
}

// Use trait objects for runtime polymorphism
pub trait Handler: Send + Sync {
    fn handle(&self, request: Request) -> Response;
}

// Use generics for static dispatch
pub fn process<T: AsRef<[u8]>>(data: T) -> Result<()> {
    let bytes = data.as_ref();
    // ...
}
Async Patterns
// Use async traits with async-trait crate (until native support)
#[async_trait]
pub trait Service {
    async fn call(&self, request: Request) -> Response;
}

// Structured concurrency with tokio
async fn process_batch(items: Vec<Item>) -> Vec<Result<Output>> {
    let futures: Vec<_> = items
        .into_iter()
        .map(|item| async move { process_item(item).await })
        .collect();

    futures::future::join_all(futures).await
}

// Cancellation-safe operations
async fn with_timeout<T, F>(duration: Duration, fut: F) -> Result<T>
where
    F: Future<Output = T>,
{
    tokio::time::timeout(duration, fut)
        .await
        .map_err(|_| Error::Timeout)
}
Advanced Async Patterns
Graceful Shutdown with CancellationToken
use tokio_util::sync::CancellationToken;

async fn run_server(token: CancellationToken) -> Result<()> {
    let listener = TcpListener::bind("0.0.0.0:8080").await?;

    loop {
        tokio::select! {
            Ok((stream, _)) = listener.accept() => {
                let child_token = token.child_token();
                tokio::spawn(async move {
                    handle_connection(stream, child_token).await;
                });
            }
            _ = token.cancelled() => {
                tracing::info!("shutdown signal received, draining connections");
                break;
            }
        }
    }
    Ok(())
}

// Main: wire up OS signals to cancellation
#[tokio::main]
async fn main() -> Result<()> {
    let token = CancellationToken::new();
    let shutdown_token = token.clone();

    tokio::spawn(async move {
        tokio::signal::ctrl_c().await.ok();
        shutdown_token.cancel();
    });

    run_server(token).await
}
Structured Concurrency with JoinSet
use tokio::task::JoinSet;

async fn process_batch(items: Vec<Item>) -> Vec<Result<Output>> {
    let mut set = JoinSet::new();

    for item in items {
        set.spawn(async move { process_item(item).await });
    }

    let mut results = Vec::with_capacity(set.len());
    while let Some(res) = set.join_next().await {
        match res {
            Ok(output) => results.push(output),
            Err(join_err) => results.push(Err(join_err.into())),
        }
    }
    results
}
Backpressure with Bounded Channels
use tokio::sync::mpsc;

async fn pipeline(input: Vec<RawData>) -> Result<()> {
    // Bound the channel to apply backpressure when consumer is slow
    let (tx, mut rx) = mpsc::channel::<Processed>(64);

    // Producer: blocks when channel is full
    let producer = tokio::spawn(async move {
        for item in input {
            let processed = transform(item).await;
            if tx.send(processed).await.is_err() {
                break; // Receiver dropped, stop producing
            }
        }
    });

    // Consumer: processes at its own pace
    while let Some(item) = rx.recv().await {
        persist(item).await?;
    }

    producer.await?;
    Ok(())
}
Tower Middleware Composition
use tower::{ServiceBuilder, ServiceExt};
use tower_http::{trace::TraceLayer, timeout::TimeoutLayer};

// Stack middleware layers declaratively
let service = ServiceBuilder::new()
    .layer(TraceLayer::new_for_http())
    .layer(TimeoutLayer::new(Duration::from_secs(30)))
    .concurrency_limit(100)
    .rate_limit(1000, Duration::from_secs(1))
    .service(my_handler);

// Custom Tower Layer for retry with backoff
use tower::retry::{Retry, Policy};

#[derive(Clone)]
struct RetryPolicy {
    max_retries: usize,
}

impl<Req: Clone, Res, E> Policy<Req, Res, E> for RetryPolicy {
    type Future = futures::future::Ready<()>;

    fn retry(&mut self, _req: &mut Req, result: &mut Result<Res, E>) -> Option<Self::Future> {
        if self.max_retries > 0 && result.is_err() {
            self.max_retries -= 1;
            Some(futures::future::ready(()))
        } else {
            None
        }
    }

    fn clone_request(&mut self, req: &Req) -> Option<Req> {
        Some(req.clone())
    }
}
Disciplined Workflow Integration (Async)
  • Research phase: Identify async boundaries, cancellation requirements, and backpressure needs
  • Design phase: Specify Tower middleware stack, shutdown strategy, channel bounds
  • Verification phase: Unit test cancellation paths, verify backpressure under load, test middleware ordering
FFI and Cross-Language Integration
Safe C API Wrappers
// Opaque handle pattern: hide Rust internals behind a pointer
pub struct Engine { /* internal fields */ }

/// SAFETY: Engine is Send+Sync, and callers must not use
/// the handle after calling engine_destroy.
#[no_mangle]
pub extern "C" fn engine_create() -> *mut Engine {
    Box::into_raw(Box::new(Engine::new()))
}

#[no_mangle]
pub extern "C" fn engine_process(
    engine: *mut Engine,
    input: *const c_char,
    input_len: usize,
) -> i32 {
    // Catch panics at every FFI boundary
    std::panic::catch_unwind(|| {
        let engine = unsafe {
            assert!(!engine.is_null());
            &mut *engine
        };
        let slice = unsafe { std::slice::from_raw_parts(input as *const u8, input_len) };
        match std::str::from_utf8(slice) {
            Ok(s) => engine.process(s).map(|_| 0).unwrap_or(-1),
            Err(_) => -2, // Invalid UTF-8
        }
    })
    .unwrap_or(-99) // Panic occurred
}

#[no_mangle]
pub extern "C" fn engine_destroy(engine: *mut Engine) {
    if !engine.is_null() {
        unsafe { drop(Box::from_raw(engine)); }
    }
}
String Management Across FFI
use std::ffi::{CStr, CString};

// Receiving a C string (borrowed)
fn from_c_str(ptr: *const c_char) -> Result<&str> {
    let cstr = unsafe { CStr::from_ptr(ptr) };
    cstr.to_str().map_err(|_| Error::InvalidUtf8)
}

// Returning a string to C (caller must free)
#[no_mangle]
pub extern "C" fn engine_get_name(engine: *const Engine) -> *mut c_char {
    let engine = unsafe { &*engine };
    CString::new(engine.name())
        .map(CString::into_raw)
        .unwrap_or(std::ptr::null_mut())
}

#[no_mangle]
pub extern "C" fn engine_free_string(s: *mut c_char) {
    if !s.is_null() {
        unsafe { drop(CString::from_raw(s)); }
    }
}
WASM Interop Patterns
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub struct WasmEngine {
    inner: Engine,
}

#[wasm_bindgen]
impl WasmEngine {
    #[wasm_bindgen(constructor)]
    pub fn new() -> Self {
        Self { inner: Engine::new() }
    }

    pub fn process(&mut self, input: &str) -> Result<JsValue, JsError> {
        let result = self.inner.process(input)?;
        Ok(serde_wasm_bindgen::to_value(&result)?)
    }
}

// Conditional compilation for WASM vs native
#[cfg(target_arch = "wasm32")]
pub fn get_time() -> f64 {
    js_sys::Date::now()
}

#[cfg(not(target_arch = "wasm32"))]
pub fn get_time() -> f64 {
    std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .unwrap()
        .as_secs_f64() * 1000.0
}
uniffi for Automatic Bindings
// In src/lib.rs with uniffi scaffolding
uniffi::setup_scaffolding!();

#[derive(uniffi::Object)]
pub struct SearchEngine { /* ... */ }

#[uniffi::export]
impl SearchEngine {
    #[uniffi::constructor]
    pub fn new(config: SearchConfig) -> Self { /* ... */ }

    pub fn search(&self, query: &str) -> Vec<SearchResult> { /* ... */ }
}

#[derive(uniffi::Record)]
pub struct SearchConfig {
    pub max_results: u32,
    pub case_sensitive: bool,
}
Disciplined Workflow Integration (FFI)
  • Research phase: Audit all extern blocks and unsafe FFI for safety gaps
  • Design phase: Specify ownership transfer semantics at every FFI boundary
  • Verification phase: Miri + fuzzing mandatory for any new FFI surface; property tests for string conversion roundtrips

Crate Recommendations

CategoryCratePurpose
Async RuntimetokioIndustry standard async runtime
Async Utilitiestokio-utilCancellationToken, codec helpers
MiddlewaretowerService trait, layers, retry, rate limiting
HTTP Middlewaretower-httpTrace, timeout, compression layers for HTTP
SerializationserdeDe/serialization framework
HTTP ClientreqwestAsync HTTP client
HTTP ServeraxumErgonomic web framework
CLIclapCommand-line parsing
LoggingtracingStructured logging/tracing
Error HandlingthiserrorDerive Error implementations
Error ContextanyhowApplication error handling
TestingproptestProperty-based testing
MockingmockallMock generation
WASM Bindingswasm-bindgenRust-to-JS FFI for WebAssembly
WASM Serdeserde-wasm-bindgenSerialize Rust types to JsValue
Multi-Language FFIuniffiAuto-generate Kotlin/Swift/Python bindings

Common Pitfalls

  1. Overusing clone() - Often indicates design issues
  2. Ignoring lifetimes - They communicate important constraints
  3. Blocking in async - Use spawn_blocking for CPU work
  4. Panic in libraries - Return errors instead
  5. Stringly-typed APIs - Use newtypes and enums

Unsafe Code Policy

Unsafe code is allowed only when necessary and must follow strict guidelines:

Requirements
  1. Isolate unsafe behind a small, well-tested module boundary
  2. Document every unsafe block with:
    • What invariants must hold
    • Why safe Rust cannot express this
    • How correctness is verified (tests, fuzzing)
/// SAFETY: This module provides safe wrappers around raw pointer operations.
/// All public functions maintain the following invariants:
/// - Pointers are always valid and aligned
/// - Lifetimes are correctly bounded
/// - No data races possible (single-threaded access enforced)
mod raw_buffer {
    /// SAFETY: `ptr` must be valid for reads of `len` bytes.
    /// The caller must ensure the memory is initialized.
    /// This is verified by: unit tests, property tests, and fuzzing.
    pub unsafe fn read_unchecked(ptr: *const u8, len: usize) -> Vec<u8> {
        // implementation
    }
}
Prefer Safe Alternatives

Before writing unsafe:

  1. Check if the standard library has a safe API
  2. Check well-audited crates (e.g., zerocopy, bytemuck)
  3. Only use bespoke pointer tricks if benchmark data demands it
Testing Unsafe Code
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn raw_buffer_valid_input() {
        // Test the happy path
    }

    #[test]
    fn raw_buffer_empty_input() {
        // Edge case: zero length
    }

    // Property-based test to find edge cases
    proptest! {
        #[test]
        fn raw_buffer_never_panics(data: Vec<u8>) {
            // Should never panic or cause UB
        }
    }
}

Error Handling for CLI/Tools

For user-facing tools, errors must be actionable:

// BAD: Unhelpful error
Err(Error::Failed)

// GOOD: Actionable error with context
#[derive(Debug, thiserror::Error)]
pub enum ConfigError {
    #[error("config file not found at {path}: run `app init` to create one")]
    NotFound { path: PathBuf },

    #[error("invalid config at {path}:{line}: {message}")]
    Parse { path: PathBuf, line: usize, message: String },

    #[error("permission denied reading {path}: check file permissions with `ls -la {path}`")]
    PermissionDenied { path: PathBuf },
}

Rules:

  • Errors must explain what failed AND how to fix it
  • Use structured error types (thiserror) for public APIs
  • Never silently ignore I/O or UTF-8 errors - document behavior and test it
  • Keep error context through the call stack (anyhow for applications)

Constraints

  • No unwrap() in library code (use expect() with reason or proper error handling)
  • No unsafe without documented invariants and tests
  • No blocking calls in async context
  • No #![deny(warnings)] in source (use CI flags)
  • Minimize use of Rc/RefCell (prefer ownership)
  • Avoid excessive generics (impacts compile time)
  • Split modules at ~1000 lines if concerns are distinct

Success Metrics

  • Code compiles without warnings
  • Passes cargo fmt --check and cargo clippy cleanly
  • All #[allow(...)] annotations are justified in comments
  • No performance regressions (verified by benchmarks)
  • Unsafe code is isolated, documented, and tested
  • API is intuitive for users
  • Error messages are actionable for end users
  • Documentation is comprehensive

Comments

Markdown supported