# Claude Skill Rust

> Guide for Rust development including code style, testing, building, and quality checks using cargo tools. Apply when working with Rust code, Cargo.toml, or running cargo commands.

- Skill: `londey/claude-skill-rust` (Agent Skill, multi-file: 3 files)
- Install (CLI): `npx skillmds@latest add londey/claude-skill-rust`
- Raw SKILL.md: https://api.skillmd.com/api/skills/londey/claude-skill-rust/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: londey (https://skillmd.com/u/londey)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/londey/claude-skill-rust

---


# Rust Development Guide

## Code Style

- Follow standard Rust conventions and idioms
- Use `rustfmt` for code formatting
- Configure lints in `lib.rs` (see Crate-Level Lint Configuration below)
- Prefer descriptive variable and function names

## Module File Organization

Prefer the modern `<module_name>.rs` style over the legacy `mod.rs` style for module files.

**Preferred (modern style):**

```text
src/
├── lib.rs
├── config.rs        # mod config
├── config/
│   └── parser.rs    # mod config::parser
├── network.rs       # mod network
└── network/
    ├── client.rs    # mod network::client
    └── server.rs    # mod network::server
```

**Avoid (legacy style):**

```text
src/
├── lib.rs
├── config/
│   ├── mod.rs       # mod config
│   └── parser.rs    # mod config::parser
└── network/
    ├── mod.rs       # mod network
    ├── client.rs    # mod network::client
    └── server.rs    # mod network::server
```

Benefits of the modern style:

- File names directly indicate the module name (no ambiguous `mod.rs` files)
- Easier navigation in editors and file browsers
- Clear correspondence between module path and file path
- Supported since Rust 2018 edition

## Module Visibility and Re-exports

Do not export internal modules with `pub mod`. Keep modules private (`mod foo;`)
and publish only a curated public surface by re-exporting the specific items at
the crate root with `pub use`. This keeps the module layout an implementation
detail you can refactor freely, gives users one flat, stable import path
(`use my_crate::Thing;`), and prevents internal helper types from leaking into
the public API through the module namespace.

**Preferred:**

```rust
// lib.rs
mod packed;                       // private module
mod widget;                       // private module

pub use packed::{PackedArray, PackedVec};   // curated public surface
pub use widget::Widget;

#[cfg(feature = "alloc")]
pub use packed::PackedBitsVec;    // gate re-exports the same way as the items
```

**Avoid:**

```rust
pub mod packed;   // exposes the whole module namespace and its path structure
pub mod widget;   // as public API, including items you only meant to share
                  // internally
```

Guidance:

- Users should import from the crate root (`use my_crate::PackedArray`), never
  through a module path (`use my_crate::packed::PackedArray`).
- Gate a re-export with the same `#[cfg(...)]` as the item it re-exports.
- If an implementation-detail trait or type must be `pub` only because it
  appears in a public item's signature (Rust rejects a more-private type in a
  public interface, `E0446`), leave it `pub` but **do not re-export it**. Living
  in a private, non-re-exported module makes it unreachable to downstream crates
  even though the keyword is `pub`. Sealing it (a `pub` trait with a private
  supertrait) additionally prevents external implementations.
- The exception is a module that is *intended* as a namespaced public API
  grouping (e.g. `prelude`); make that `pub` deliberately, not by default.

## Crate-Level Lint Configuration

Define lint rules at the top of `lib.rs` (or `main.rs` for binaries) rather than via command-line flags. This ensures consistent enforcement and documents project standards.

```rust
#![deny(unsafe_code)]
#![cfg_attr(all(not(debug_assertions), not(test)), deny(clippy::all))]
#![cfg_attr(all(not(debug_assertions), not(test)), deny(clippy::pedantic))]
#![cfg_attr(all(not(debug_assertions), not(test)), deny(missing_docs))]
// Allow some pedantic lints that are too strict for this project
#![allow(clippy::module_name_repetitions)]
#![allow(clippy::must_use_candidate)]
#![allow(clippy::missing_errors_doc)]
#![allow(clippy::missing_panics_doc)]
#![allow(clippy::enum_variant_names)]
// Until 1.0.0, allow dead code and unused dependency warnings
#![allow(dead_code)]
#![allow(unused_crate_dependencies)]

```

Key principles:

- Deny `unsafe_code` unless explicitly required
- Use `cfg_attr` to deny lints only in release builds (not debug or test)
- Allow specific pedantic lints that conflict with project conventions
- Document why each `allow` is necessary

**Important**: These are good defaults for new crates. Do not override existing lint configurations - if a crate already specifies `deny` or `allow` for a rule, respect that choice.

## Error Handling

Avoid `.unwrap()` and `.expect()` - these cause panics and should not be used in production code. Instead:

- Use `Result<T, E>` return types with the `?` operator for error propagation
- Prefer early returns for error conditions
- Handle errors explicitly at appropriate boundaries

### Error Type Crates

**Note**: The following recommendations apply to `std` crates only. For `no_std` embedded targets, use custom error enums or `thiserror` with `default-features = false`.

- **Libraries**: Use `thiserror` to define custom error types with derive macros
- **Applications**: Use `anyhow` for convenient error handling with context

Example library error type with `thiserror`:

```rust
use thiserror::Error;

/// Errors that can occur during data processing.
#[derive(Debug, Error)]
pub enum ProcessError {
    /// The input data was empty.
    #[error("input data cannot be empty")]
    EmptyInput,

    /// Failed to parse the input data.
    #[error("failed to parse input: {0}")]
    ParseFailure(String),

    /// An I/O error occurred.
    #[error("I/O error: {0}")]
    Io(#[from] std::io::Error),
}
```

Example application error handling with `anyhow`:

```rust
use anyhow::{Context, Result};

fn load_config(path: &Path) -> Result<Config> {
    let contents = std::fs::read_to_string(path)
        .context("failed to read config file")?;

    let config: Config = serde_json::from_str(&contents)
        .context("failed to parse config JSON")?;

    Ok(config)
}
```

## Logging

**Note**: The following recommendations apply to `std` crates only. For `no_std` embedded targets, use `defmt` or platform-specific logging mechanisms.

Avoid `println!` and `eprintln!` outside of main application entry points. Use the `log` crate for structured logging instead.

```rust
use log::{debug, info, warn, error};

fn process_data(data: &[u8]) -> Result<(), ProcessError> {
    debug!("Processing {} bytes", data.len());

    if data.is_empty() {
        warn!("Received empty data buffer");
        return Err(ProcessError::EmptyInput);
    }

    info!("Data processed successfully");
    Ok(())
}
```

Log levels:

- `error!` - Unrecoverable errors or failures
- `warn!` - Unexpected conditions that are handled
- `info!` - Significant events in normal operation
- `debug!` - Detailed information for debugging
- `trace!` - Very detailed tracing information

Applications should initialise a log implementation (e.g., `env_logger`, `tracing-subscriber`) in `main()`.

## Whitespace and Formatting

Maintain blank lines for readability:

- Between module-level items (functions, structs, enums, traits, constants, impl blocks)
- Between struct and enum members when they have doc comments
- Between constant declarations
- After `use` statements before the first item
- After code blocks (loops, conditionals, match arms) before subsequent statements

### Struct Field Formatting

When struct or enum fields have documentation comments, add a blank line before each doc comment to visually separate the fields:

```rust
pub struct Handle<T> {
    /// Index into the arena's resource vector.
    index: usize,

    /// Generation counter to detect stale handles after resource reuse.
    generation: u32,

    /// Type marker to prevent mixing handles of different resource types.
    _marker: PhantomData<T>,
}
```

For fields without documentation, blank lines are optional but may still aid readability for complex types.

## Documentation

All items require rustdoc documentation comments.

For documenting the following item (functions, types, constants, fields):

```rust
/// Documentation for the item below.
```

For documenting the enclosing item (modules, crate root):

```rust
//! Documentation for the containing module.
```

Items requiring documentation include:

- Functions and methods
- Types (structs, enums, type aliases)
- Traits and trait implementations
- Constants and statics
- Modules
- Test functions
- Struct and enum fields

### Function Documentation

Function documentation must include:

- Brief description of purpose
- `# Arguments` section documenting each parameter
- `# Returns` section documenting the return value
- `# Errors` section if the function returns `Result`
- `# Panics` section if the function can panic

Example:

```rust
/// Calculates the checksum for the given data buffer.
///
/// # Arguments
///
/// * `data` - The byte slice to calculate the checksum for.
/// * `seed` - Initial seed value for the checksum algorithm.
///
/// # Returns
///
/// The computed 32-bit checksum value.
///
/// # Errors
///
/// Returns `ChecksumError::EmptyBuffer` if `data` is empty.
fn calculate_checksum(data: &[u8], seed: u32) -> Result<u32, ChecksumError> {
    // ...
}
```

### Constant Documentation

Constant documentation must include:

- Description of the constant's purpose
- Reference to specification, document, or section where applicable

Example:

```rust
/// Maximum transmission unit size in bytes.
///
/// Per RFC 894, Section 3 - Ethernet frames have a maximum payload of 1500 bytes.
const MTU_SIZE: usize = 1500;
```

## Testing

- Write unit tests in the same file as the code being tested
- Use integration tests in the `tests/` directory for end-to-end functionality
- Run tests with `cargo test`
- Aim for meaningful test coverage of core functionality
- Document test functions with their purpose and what they verify

## Build Verification

After completing changes to Rust code, run the following checks in order:

1. **Format Code**: `cargo fmt`
2. **Format Check**: `cargo fmt --check`
3. **Lint Check**: `cargo clippy -- -D warnings`
4. **Tests**: `cargo test`
5. **Release Build**: `cargo build --release`
6. **Documentation**: `cargo doc --no-deps --document-private-items`
7. **License Check**: `cargo deny check`
8. **Security Audit**: `cargo audit`

If any check fails, fix the issues before proceeding.

## Security Tools

Projects should use these security tools:

- **cargo-deny**: License and advisory checker. Install with `cargo install cargo-deny`. Configuration in `deny.toml` enforces permissive licenses (MIT, Apache-2.0, BSD) and denies copyleft licenses (GPL, LGPL, AGPL).

- **cargo-audit**: Security vulnerability scanner. Install with `cargo install cargo-audit`. Scans dependencies against the RustSec Advisory Database.

## Dependencies

When adding dependencies:

- Choose well-maintained crates widely used in the Rust ecosystem
- For workspaces, add shared dependencies to `[workspace.dependencies]` in root `Cargo.toml`
- Justify each dependency with a clear use case
- Disable default features and explicitly enable only required features

### Minimising Dependencies

Always add dependencies with `default-features = false` and explicitly specify the features you need. This reduces compile times and binary size by avoiding unnecessary transitive dependencies.

```toml
# Prefer this:
serde = { version = "1.0", default-features = false, features = ["derive"] }

# Avoid this:
serde = "1.0"
```

When adding a new dependency:

1. Check the crate's documentation for available features
2. Identify the minimum set of features required for your use case
3. Add with `default-features = false`
4. Explicitly list only the features you need

