rust-development

Expert Rust development guidance covering naming conventions, type safety, error handling, documentation, and best practices based on official Rust guidelines.

cosmonic-labs/skills5 installsApache-2.0Synced Aug 27

Works with

Claude CodeCursorCodex CLIGitHub CopilotGemini CLI
---
name: rust-development
description: Expert Rust development guidance covering naming conventions, type safety, error handling, documentation, and best practices based on official Rust guidelines.
license: Apache-2.0
---

# Rust Development

This skill enforces new code or code changes to conform to proper Rust guidelines.

## When to Use

Activate this skill when:
- Writing new Rust code
- Reviewing or modifying existing Rust code
- Designing Rust APIs, libraries, or applications
- Refactoring Rust codebases

**Required before submitting code:**
- All code must pass `cargo clippy -- -D warnings`
- All code must be formatted with `cargo +nightly fmt`

## Guidelines Sources

These guidelines are based on:
- [Microsoft Rust Guidelines](https://microsoft.github.io/rust-guidelines/)
- [Rust API Guidelines](https://rust-lang.github.io/api-guidelines/)
- [The Rust Programming Language Book](https://doc.rust-lang.org/book/)
- Rust community best practices

---

## Naming Conventions (RFC 430)

### Case Rules

| Item | Convention | Example |
|------|------------|---------|
| Modules | `snake_case` | `my_module` |
| Types & Traits | `UpperCamelCase` | `MyStruct`, `MyTrait` |
| Enum variants | `UpperCamelCase` | `Some`, `None` |
| Functions & Methods | `snake_case` | `do_something` |
| Macros | `snake_case!` | `my_macro!` |
| Statics & Constants | `SCREAMING_SNAKE_CASE` | `MAX_SIZE` |
| Type parameters | Single uppercase letter | `T`, `U`, `E` |
| Lifetimes | Single lowercase letter with `'` | `'a`, `'b` |

### Important Rules

- Acronyms count as one word: use `Uuid`, not `UUID`; `HttpRequest`, not `HTTPRequest`
- In `snake_case`, single-letter words only appear last: `btree_map`, not `b_tree_map`
- Avoid `-rs` or `-rust` suffixes in crate names
- Feature names should be direct: use `std`, not `use-std`

### Conversion Methods

| Prefix | Cost | Ownership | Example |
|--------|------|-----------|---------|
| `as_` | Free | borrowed → borrowed | `as_str()`, `as_bytes()` |
| `to_` | Expensive | borrowed → owned | `to_string()`, `to_vec()` |
| `into_` | Variable | owned → owned (non-Copy) | `into_inner()`, `into_vec()` |

The `mut` qualifier goes where it would in the return type: `as_mut_slice()`, not `as_slice_mut()`.

### Getter Methods

- Omit the `get_` prefix: use `first()`, not `get_first()`
- Reserve `get` for single, obvious values like `Cell::get`

### Iterator Methods

Collections should provide:
```rust
fn iter(&self) -> Iter<'_, T>       // borrowed iteration
fn iter_mut(&mut self) -> IterMut<'_, T>  // mutable iteration
fn into_iter(self) -> IntoIter<T>  // owned iteration
```

Iterator type names must match their producing methods.

---

## Type Safety

### Use Newtypes for Distinctions

Create wrapper types to distinguish values with the same underlying type:

```rust
// Bad: easy to confuse
fn set_dimensions(width: u32, height: u32) { }

// Good: compiler catches mistakes
struct Width(u32);
struct Height(u32);
fn set_dimensions(width: Width, height: Height) { }
```

### Avoid Primitive Obsession

Use custom types instead of `bool`, `u8`, or `Option` when meaning is unclear:

```rust
// Bad: what do these bools mean?
Widget::new(true, false)

// Good: self-documenting
enum Size { Small, Large }
enum Shape { Round, Square }
Widget::new(Size::Small, Shape::Round)
```

### Bitflags for Multiple Flags

Use the `bitflags` crate for independent flags:

```rust
use bitflags::bitflags;

bitflags! {
    struct Permissions: u32 {
        const READ = 0b001;
        const WRITE = 0b010;
        const EXECUTE = 0b100;
    }
}
```

### Builders for Complex Construction

For types with many optional parameters:

```rust
struct Config {
    timeout: Duration,
    retries: u32,
    // ...
}

struct ConfigBuilder { /* ... */ }

impl ConfigBuilder {
    fn new() -> Self { /* ... */ }
    fn timeout(mut self, timeout: Duration) -> Self { /* ... */ }
    fn retries(mut self, retries: u32) -> Self { /* ... */ }
    fn build(self) -> Result<Config, Error> { /* ... */ }
}
```

---

## Trait Implementations

### Common Traits to Implement

Types should eagerly implement applicable standard traits:

| Trait | When to Implement |
|-------|-------------------|
| `Clone` | Almost always |
| `Copy` | For small, trivially copyable types |
| `Debug` | Always for public types |
| `Default` | When a sensible default exists |
| `PartialEq`, `Eq` | For comparable types |
| `PartialOrd`, `Ord` | For orderable types |
| `Hash` | For types used as map keys |
| `Display` | For user-facing output |
| `Send`, `Sync` | When thread-safe |

### Conversion Traits

```rust
// Implement From for infallible conversions
impl From<&str> for MyString { }

// Implement TryFrom for fallible conversions
impl TryFrom<&str> for MyValidatedString {
    type Error = ValidationError;
}

// Implement AsRef/AsMut for borrowing
impl AsRef<str> for MyString { }

// Never implement Into/TryInto directly - they're derived automatically
```

### Collection Traits

Collections should implement:
- `FromIterator` - enables `collect()`
- `Extend` - enables `extend()`
- `IntoIterator` - enables `for` loops

### Serialization

Implement Serde traits behind a feature flag:

```toml
[features]
serde = ["dep:serde"]

[dependencies]
serde = { version = "1", features = ["derive"], optional = true }
```

```rust
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct MyType { }
```

### Object Safety for Trait Objects

If a trait may be useful as a trait object (`dyn Trait`), ensure it's object-safe:

```rust
// Object-safe: can be used as dyn Trait
trait Drawable {
    fn draw(&self);
    fn bounds(&self) -> Rect;
}

// NOT object-safe: has generic method
trait Processor {
    fn process<T>(&self, item: T);  // Generics not allowed
}

// NOT object-safe: returns Self
trait Clonable {
    fn clone(&self) -> Self;  // Self by value not allowed
}
```

### Only Smart Pointers Implement Deref

Only implement `Deref` and `DerefMut` for smart pointer types:

```rust
// Good: Smart pointer implementing Deref
struct MyBox<T>(T);

impl<T> Deref for MyBox<T> {
    type Target = T;
    fn deref(&self) -> &T { &self.0 }
}

// Bad: Non-pointer type implementing Deref
struct Email(String);
impl Deref for Email {
    type Target = str;  // Don't do this - use AsRef instead
    fn deref(&self) -> &str { &self.0 }
}
```

Use `AsRef` instead for non-pointer types that provide access to inner data.

### Debug Output Should Never Be Empty

`Debug` implementations should always produce meaningful output:

```rust
// Bad: Empty debug output
impl fmt::Debug for MyType {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        Ok(())  // Produces nothing
    }
}

// Good: At minimum show type name
impl fmt::Debug for MyType {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("MyType").finish()
    }
}
```

---

## Error Handling

### Error Type Requirements

Error types must:
- Implement `std::error::Error`
- Implement `Send + Sync`
- Implement `Display` with lowercase message, no trailing punctuation
- Never use `()` as an error type

```rust
use std::error::Error;
use std::fmt;

#[derive(Debug)]
pub struct MyError {
    kind: ErrorKind,
    source: Option<Box<dyn Error + Send + Sync>>,
}

impl fmt::Display for MyError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "operation failed: {}", self.kind)  // lowercase, no period
    }
}

impl Error for MyError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        self.source.as_ref().map(|e| e.as_ref() as _)
    }
}
```

### Use `thiserror` for Library Errors

```rust
use thiserror::Error;

#[derive(Debug, Error)]
pub enum MyError {
    #[error("failed to read file: {path}")]
    ReadFile { path: String, source: std::io::Error },

    #[error("invalid configuration")]
    InvalidConfig,
}
```

### Use `anyhow` for Application Errors

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

fn main() -> Result<()> {
    let config = read_config()
        .context("failed to read configuration")?;
    Ok(())
}
```

### Propagate with `?`, Not `unwrap()`

```rust
// Bad
let file = File::open(path).unwrap();

// Good
let file = File::open(path)?;

// Good with context
let file = File::open(path)
    .with_context(|| format!("failed to open {}", path.display()))?;
```

### Use `let ... else` for Early Returns

Extract values and exit early to keep the happy path unindented:

```rust
// Bad: Nested conditionals
fn process(value: Option<String>) -> Result<(), Error> {
    if let Some(s) = value {
        if !s.is_empty() {
            // happy path buried in nesting
            do_work(&s)?;
        }
    }
    Ok(())
}

// Good: let-else keeps happy path flat
fn process(value: Option<String>) -> Result<(), Error> {
    let Some(s) = value else {
        return Ok(());
    };

    if s.is_empty() {
        return Ok(());
    }

    do_work(&s)?;
    Ok(())
}
```

Benefits:
- Keeps the main logic at the top level of indentation
- Makes early exit conditions explicit
- Reduces rightward drift in complex functions

---

## Documentation

### Requirements

- Every public item must have documentation
- Crate-level docs (`//!` in lib.rs) should provide overview and examples
- Include examples that compile and run

### Function Documentation Sections

```rust
/// Brief description of what this function does.
///
/// More detailed explanation if needed.
///
/// # Arguments
///
/// * `arg1` - Description of first argument
///
/// # Returns
///
/// Description of return value.
///
/// # Errors
///
/// Returns `Err` if:
/// - condition one occurs
/// - condition two occurs
///
/// # Panics
///
/// Panics if:
/// - some invariant is violated
///
/// # Safety
///
/// This function is unsafe because:
/// - caller must ensure X
///
/// # Examples
///
/// ```
/// use my_crate::my_function;
///
/// let result = my_function(42)?;
/// assert_eq!(result, 84);
/// # Ok::<(), my_crate::Error>(())
/// ```
pub fn my_function(arg1: i32) -> Result<i32, Error> { }
```

### Example Guidelines

- Use `?` for error handling, not `unwrap()` or `try!`
- Hide boilerplate with `# ` prefix
- Ensure examples compile with `cargo test --doc`

### Hide Implementation Details

Use `#[doc(hidden)]` to hide internal items from documentation:

```rust
// Public but hidden from docs
#[doc(hidden)]
pub mod __internal {
    // Implementation details needed by macros
}

// Hide re-exports of internal types
#[doc(hidden)]
pub use crate::internal::Helper;
```

Don't expose helper types, internal modules, or implementation details in public documentation.

---

## Memory and Safety

### Minimize `unsafe`

- Avoid `unsafe` when safe alternatives exist
- Isolate `unsafe` code in small, well-documented functions
- Document all safety invariants

```rust
/// Returns a reference to the element at `index`.
///
/// # Safety
///
/// Caller must ensure `index < self.len()`.
pub unsafe fn get_unchecked(&self, index: usize) -> &T {
    // SAFETY: caller guarantees index is in bounds
    unsafe { self.data.get_unchecked(index) }
}
```

### Prefer Owned Types for Thread Safety

- Return owned types when the data might be used across threads
- Document `Send` and `Sync` bounds explicitly

### Avoid Interior Mutability Unless Necessary

- Prefer `&mut self` methods over `Cell`/`RefCell`
- When using interior mutability, document why it's needed

---

## Performance

### Avoid Unnecessary Allocations

```rust
// Bad: allocates unnecessarily
fn greet(name: &str) -> String {
    format!("Hello, {}!", name.to_string())
}

// Good: reuses the input
fn greet(name: &str) -> String {
    format!("Hello, {name}!")
}
```

### Use Iterators Over Indexing

```rust
// Bad: bounds checking on each access
for i in 0..vec.len() {
    process(vec[i]);
}

// Good: optimized iteration
for item in &vec {
    process(item);
}
```

### Prefer `&str` Over `String` in Parameters

```rust
// Bad: forces caller to allocate
fn process(s: String) { }

// Good: accepts both String and &str
fn process(s: &str) { }

// Good: generic over string types
fn process(s: impl AsRef<str>) { }
```

### Use `Cow` for Conditional Ownership

```rust
use std::borrow::Cow;

fn process(input: &str) -> Cow<'_, str> {
    if needs_modification(input) {
        Cow::Owned(modify(input))
    } else {
        Cow::Borrowed(input)
    }
}
```

---

## API Design

### Functions Should Not Take Out-Parameters

```rust
// Bad: out-parameter
fn get_values(output: &mut Vec<i32>) { }

// Good: return the value
fn get_values() -> Vec<i32> { }
```

### Expose Intermediate Results

Allow callers to avoid duplicate work:

```rust
// Bad: forces re-parsing
fn is_valid(s: &str) -> bool { }
fn parse(s: &str) -> Result<Value, Error> { }

// Good: parse once, check validity from result
fn parse(s: &str) -> Result<Value, Error> { }
// Caller can check is_ok() for validity
```

### Constructors Are Static Methods

```rust
impl MyType {
    // Primary constructor
    pub fn new() -> Self { }

    // Alternative constructors
    pub fn with_capacity(cap: usize) -> Self { }
    pub fn from_parts(a: A, b: B) -> Self { }
}
```

### Smart Pointers Don't Add Inherent Methods

Types implementing `Deref` should not add methods that might conflict with the target type.

### Use Generics to Minimize Assumptions

Accept generic parameters instead of concrete types when possible:

```rust
// Bad: Requires specific type
fn read_config(file: File) -> Config { }

// Good: Accepts any reader
fn read_config(reader: impl Read) -> Config { }

// Good: Accepts any string-like type
fn greet(name: impl AsRef<str>) {
    println!("Hello, {}", name.as_ref());
}
```

### Validate Function Arguments

Functions should validate their inputs rather than trusting callers:

```rust
// Bad: Trusts caller to validate
fn get_element(slice: &[u8], index: usize) -> u8 {
    slice[index]  // Panics on invalid index
}

// Good: Returns Option for invalid input
fn get_element(slice: &[u8], index: usize) -> Option<u8> {
    slice.get(index).copied()
}

// Good: Use types that enforce validity
fn process_nonempty(items: NonEmpty<Item>) { }
```

---

## Project Structure

### Cargo.toml Metadata

```toml
[package]
name = "my-crate"
version = "0.1.0"
edition = "2021"
rust-version = "1.70"  # MSRV
authors = ["Author Name <email@example.com>"]
description = "Brief description of the crate"
documentation = "https://docs.rs/my-crate"
repository = "https://github.com/user/my-crate"
license = "MIT OR Apache-2.0"
keywords = ["keyword1", "keyword2"]
categories = ["category1"]

[dependencies]
# Use specific versions or version ranges
serde = { version = "1.0", features = ["derive"] }

[dev-dependencies]
# Test-only dependencies
```

### Module Organization

```
src/
├── lib.rs          # Public API, re-exports
├── error.rs        # Error types
├── types.rs        # Core types
├── internal/       # Private implementation
│   ├── mod.rs
│   └── helpers.rs
└── tests/          # Integration test helpers
```

### Feature Flags

- Use feature flags for optional functionality
- Name features clearly without placeholder words
- Document features in Cargo.toml and README

```toml
[features]
default = []
serde = ["dep:serde"]
async = ["dep:tokio"]
```

---

## Testing

### Test Organization

```rust
// Unit tests in the same file
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_basic_functionality() {
        // ...
    }
}

// Integration tests in tests/ directory
// tests/integration_test.rs
```

### Test Naming

```rust
#[test]
fn function_name_condition_expected_result() {
    // e.g., parse_valid_input_returns_value
}
```

### Property-Based Testing

Consider using `proptest` or `quickcheck` for property-based tests:

```rust
use proptest::prelude::*;

proptest! {
    #[test]
    fn roundtrip_serialization(value: MyType) {
        let serialized = serialize(&value);
        let deserialized = deserialize(&serialized)?;
        prop_assert_eq!(value, deserialized);
    }
}
```

---

## Future Proofing

### Use Private Fields

Structs should have private fields to allow future changes without breaking users:

```rust
// Bad: Public fields lock in representation
pub struct Point {
    pub x: f64,
    pub y: f64,
}

// Good: Private fields with accessors
pub struct Point {
    x: f64,
    y: f64,
}

impl Point {
    pub fn new(x: f64, y: f64) -> Self { Self { x, y } }
    pub fn x(&self) -> f64 { self.x }
    pub fn y(&self) -> f64 { self.y }
}
```

### Sealed Traits

Use sealed traits to prevent downstream implementations while allowing use:

```rust
mod private {
    pub trait Sealed {}
}

/// This trait is sealed and cannot be implemented outside this crate.
pub trait MyTrait: private::Sealed {
    fn method(&self);
}

// Only types in this crate can implement MyTrait
impl private::Sealed for MyType {}
impl MyTrait for MyType {
    fn method(&self) { }
}
```

---

## Dependability

### Destructors Must Not Fail

`Drop` implementations must not panic or fail:

```rust
impl Drop for Resource {
    fn drop(&mut self) {
        // Bad: Can panic
        self.file.flush().unwrap();

        // Good: Log error but don't panic
        if let Err(e) = self.file.flush() {
            eprintln!("warning: failed to flush: {e}");
        }
    }
}
```

If cleanup can fail, provide an explicit `close()` method that returns `Result`.

---

## Clippy and Formatting

### Required Clippy Lints

Enable these in `lib.rs` or `main.rs`:

```rust
#![warn(
    clippy::all,
    clippy::pedantic,
    clippy::nursery,
    missing_docs,
    rust_2018_idioms,
)]

#![allow(
    clippy::module_name_repetitions,  // if needed
)]
```

### Rustfmt Configuration

Create `rustfmt.toml`:

```toml
edition = "2021"
max_width = 100
use_small_heuristics = "Default"
```

### Required: Run Before Committing

All code must pass these checks before being submitted:

```bash
# Format code (required)
cargo +nightly fmt

# Check for lint errors (required - must pass with no warnings)
cargo clippy -- -D warnings

# Run tests
cargo test

# Verify docs build
cargo doc --no-deps
```

Code that fails clippy or is not properly formatted should not be committed.

---

## Checklist for Code Review

### Required
- [ ] Code passes `cargo clippy -- -D warnings`
- [ ] Code is formatted with `cargo +nightly fmt`
- [ ] Tests pass with `cargo test`

### Naming
- [ ] Follows RFC 430 case conventions
- [ ] Conversions use `as_`/`to_`/`into_` correctly
- [ ] No `get_` prefix on simple getters
- [ ] Iterator methods follow conventions

### Types
- [ ] Uses newtypes for distinct concepts
- [ ] Avoids bool/Option parameters with unclear meaning
- [ ] Implements appropriate standard traits
- [ ] Error types implement `Error + Send + Sync`

### Safety
- [ ] Minimizes `unsafe` code
- [ ] Documents safety invariants
- [ ] Validates inputs at boundaries

### Documentation
- [ ] All public items documented
- [ ] Examples compile and demonstrate usage
- [ ] Error/panic conditions documented

### Performance
- [ ] Avoids unnecessary allocations
- [ ] Uses iterators appropriately
- [ ] Takes references where ownership isn't needed

### Testing
- [ ] Unit tests for core logic
- [ ] Integration tests for public API
- [ ] Edge cases covered

More Writing & Documentation skills

paper-context-resolver

lllllllama/rigorpilot-skills

Rigor Paper Context helper for README-first deep learning repo reproduction. Use only when the README and repository files leave a narrow reproduction-critical gap and the task is to resolve a specific paper detail such as dataset split, preprocessing, evaluation protocol, checkpoint mapping, or runtime assumption from primary paper sources while recording conflicts. Do not use for general paper summary, repo scanning, environment setup, command execution, title-only paper lookup, or replacing README guidance by default.

450.8k

repo-intake-and-plan

lllllllama/rigorpilot-skills

Rigor Intake helper for README-first deep learning repo reproduction. Use when the task is specifically to scan a repository, read the README and common project files, extract documented commands, classify inference, evaluation, and training candidates, and return the smallest trustworthy reproduction plan to the main orchestrator. Do not use for environment setup, asset download, command execution, final reporting, paper lookup, or end-to-end orchestration.

450.0k

minimal-run-and-audit

lllllllama/rigorpilot-skills

Rigor Run skill for README-first deep learning repo reproduction. Use when the task is specifically to capture or normalize evidence from the selected smoke test or documented inference or evaluation command and write standardized `repro_outputs/` files, including patch notes when repository files changed. Do not use for training execution, initial repo intake, generic environment setup, paper lookup, target selection, hidden scientific-meaning changes, or end-to-end orchestration by itself.

449.9k

← All Writing & Documentation skills

Check your AI visibility

One URL in, a 0–100 score and the exact fixes out.

RUN THE CHECK

Browse all the tools

15 tools across six categories
13 of them never send your data anywhere

Free · No signup · No trial clock

SEE THE DIRECTORY