Skip to content

Latest commit

 

History

History
296 lines (227 loc) · 6.56 KB

File metadata and controls

296 lines (227 loc) · 6.56 KB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Project Overview

{{crate_name}} is a Rust crate built with modern tooling, strict type safety, and zero-cost abstractions.

Project Structure

src/
├── lib.rs           # Library entry point and public API
├── main.rs          # Binary entry point (optional)
├── error.rs         # Error types (if separated)
└── ...              # Additional modules

tests/
└── integration_test.rs  # Integration tests

benches/              # Benchmarks (with criterion)
examples/             # Example programs

Build Commands

This project uses Cargo as the build system.

# Build the project
cargo build

# Build with optimizations
cargo build --release

# Run tests
cargo test

# Run tests with output
cargo test -- --nocapture

# Run specific test
cargo test test_name

# Run benchmarks
cargo bench

# Run linting
cargo clippy --all-targets --all-features

# Format code
cargo fmt

# Check formatting
cargo fmt -- --check

# Generate documentation
cargo doc --open

# Check supply chain security
cargo deny check

# Run with MIRI (undefined behavior detection)
cargo +nightly miri test

# Run all checks (lint + format + test + doc + deny)
cargo fmt -- --check && cargo clippy --all-targets --all-features -- -D warnings && cargo test && cargo doc --no-deps && cargo deny check

Code Style Requirements

This project uses clippy with pedantic and nursery lints, and rustfmt for formatting.

Key Rules

  • Line length: 100 characters
  • Edition: 2024
  • MSRV: 1.80
  • Unsafe code: Forbidden unless explicitly justified
  • Panics: Not allowed in library code (unwrap, expect, panic!)

Error Handling

Always use Result types for fallible operations. Never panic in library code:

// Good - Returns Result
pub fn parse(input: &str) -> Result<Value, ParseError> {
    if input.is_empty() {
        return Err(ParseError::EmptyInput);
    }
    // parsing logic
    Ok(value)
}

// Bad - Panics
pub fn parse(input: &str) -> Value {
    input.parse().unwrap() // Never do this in library code
}

Use thiserror for custom error types:

use thiserror::Error;

#[derive(Error, Debug)]
pub enum Error {
    #[error("invalid input: {0}")]
    InvalidInput(String),

    #[error("operation failed")]
    OperationFailed {
        #[source]
        source: std::io::Error,
    },
}

Documentation

All public items must have documentation with examples:

/// Processes the input data according to the configuration.
///
/// # Arguments
///
/// * `input` - The data to process.
/// * `config` - Processing configuration.
///
/// # Returns
///
/// The processed result.
///
/// # Errors
///
/// Returns [`Error::InvalidInput`] if the input is malformed.
///
/// # Examples
///
/// ```rust
/// use {{crate_name}}::{process, Config};
///
/// let result = process("data", &Config::default())?;
/// assert!(!result.is_empty());
/// # Ok::<(), {{crate_name}}::Error>(())
/// ```
pub fn process(input: &str, config: &Config) -> Result<Output, Error> {
    // implementation
}

Ownership and Borrowing

Prefer borrowing over ownership:

// Good - borrows
pub fn process(data: &[u8]) -> Vec<u8> { ... }

// Avoid - takes ownership unnecessarily
pub fn process(data: Vec<u8>) -> Vec<u8> { ... }

Use Cow for flexible string handling:

use std::borrow::Cow;

pub fn normalize(s: &str) -> Cow<'_, str> {
    if s.contains(' ') {
        Cow::Owned(s.replace(' ', "_"))
    } else {
        Cow::Borrowed(s)
    }
}

Builder Pattern

Use builder pattern for complex configuration:

#[derive(Debug, Clone, Default)]
pub struct Config {
    timeout: Duration,
    retries: u32,
}

impl Config {
    #[must_use]
    pub const fn new() -> Self {
        Self {
            timeout: Duration::from_secs(30),
            retries: 3,
        }
    }

    #[must_use]
    pub const fn with_timeout(mut self, timeout: Duration) -> Self {
        self.timeout = timeout;
        self
    }

    #[must_use]
    pub const fn with_retries(mut self, retries: u32) -> Self {
        self.retries = retries;
        self
    }
}

Testing Conventions

  • Unit tests: Inside src/*.rs with #[cfg(test)] modules
  • Integration tests: tests/ directory
  • Doc tests: Examples in documentation
  • Property tests: Use proptest for property-based testing

Test Structure

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

    #[test]
    fn test_success_case() {
        let result = function_under_test(valid_input);
        assert_eq!(result, expected_output);
    }

    #[test]
    fn test_error_case() {
        let result = function_under_test(invalid_input);
        assert!(matches!(result, Err(Error::InvalidInput(_))));
    }
}

Property-Based Testing

use proptest::prelude::*;

proptest! {
    #[test]
    fn property_holds(input in any::<i64>()) {
        prop_assert!(predicate(input));
    }
}

Linting Configuration

Clippy is configured to deny:

  • unwrap_used, expect_used, panic - Use Result instead
  • todo, unimplemented - Complete implementation
  • dbg_macro, print_stdout, print_stderr - Use proper logging

Supply Chain Security

This project uses cargo-deny to audit dependencies:

  • Advisories: Deny crates with known vulnerabilities
  • Licenses: Only allow permissive licenses (MIT, Apache-2.0, BSD)
  • Bans: Block specific problematic crates
  • Sources: Only allow crates.io

Architecture Guidelines

  1. Zero-cost abstractions: Prefer compile-time over runtime overhead
  2. Explicit over implicit: No hidden allocations or side effects
  3. Error propagation: Use ? operator, avoid .unwrap()
  4. Const by default: Use const fn where possible
  5. Minimal dependencies: Only add what's truly needed
  6. Documentation-driven: Public API documented with examples

Performance Considerations

  • Use #[must_use] for functions returning values that should not be ignored
  • Prefer &str over String in function parameters
  • Use Vec::with_capacity() when size is known
  • Avoid allocations in hot paths
  • Profile before optimizing

CI/CD

The CI pipeline includes:

  1. Format check: cargo fmt -- --check
  2. Lint: cargo clippy --all-targets --all-features
  3. Test: cargo test --all-features
  4. Documentation: cargo doc --no-deps
  5. Supply chain: cargo deny check
  6. MSRV check: Verify minimum supported Rust version
  7. Coverage: Generate code coverage reports