home / skills / huiali / rust-skills / rust-coding

rust-coding skill

/skills/rust-coding

This skill helps you enforce Rust coding conventions and idiomatic patterns with naming, spacing, clippy, rustfmt, and documentation practices.

npx playbooks add skill huiali/rust-skills --skill rust-coding

Review the files below or copy the command above to add this skill to your agents.

Files (4)
SKILL.md
8.9 KB
---
name: rust-coding
description: Rust coding conventions expert covering naming, formatting, comments, clippy, rustfmt, lints, code style, best practices, and idiomatic patterns.
metadata:
  triggers:
    - coding convention
    - naming
    - formatting
    - clippy
    - rustfmt
    - lint
    - code style
    - best practice
    - idiomatic
    - code review
---


## Naming Conventions (Rust-Specific)

| Rule | Correct | Incorrect |
|------|---------|-----------|
| No `get_` prefix for methods | `fn name(&self)` | `fn get_name(&self)` |
| Iterator methods | `iter()` / `iter_mut()` / `into_iter()` | `get_iter()` |
| Conversion naming | `as_` (cheap), `to_` (expensive), `into_` (ownership) | Mixed usage |
| `static` variables uppercase | `static CONFIG: Config` | `static config: Config` |
| `const` variables | `const BUFFER_SIZE: usize = 1024` | No restriction |

### General Naming

```rust
// Variables and functions: snake_case
let max_connections = 100;
fn process_data() { ... }

// Types and traits: CamelCase
struct UserSession;
trait Cacheable {}

// Constants: SCREAMING_SNAKE_CASE
const MAX_CONNECTIONS: usize = 100;
static CONFIG: once_cell::sync::Lazy<Config> = ...
```


## Solution Patterns

### Pattern 1: Conversion Methods

```rust
impl Buffer {
    // as_ - cheap, view conversion
    pub fn as_slice(&self) -> &[u8] {
        &self.data
    }

    // to_ - expensive, allocating conversion
    pub fn to_vec(&self) -> Vec<u8> {
        self.data.clone()
    }

    // into_ - consuming, ownership transfer
    pub fn into_vec(self) -> Vec<u8> {
        self.data
    }
}
```

### Pattern 2: Newtype Pattern

```rust
// ✅ Domain semantics with newtypes
struct Email(String);
struct UserId(u64);
struct Meters(f64);

impl Email {
    pub fn new(s: impl Into<String>) -> Result<Self, EmailError> {
        let email = s.into();
        if email.contains('@') {
            Ok(Self(email))
        } else {
            Err(EmailError::Invalid)
        }
    }
}
```

### Pattern 3: Error Handling

```rust
// ✅ Good: propagate errors
fn read_config() -> Result<Config, ConfigError> {
    let content = std::fs::read_to_string("config.toml")
        .map_err(ConfigError::from)?;
    toml::from_str(&content)
        .map_err(ConfigError::Parse)
}

// ❌ Avoid: panic in library code
fn read_config() -> Config {
    std::fs::read_to_string("config.toml").unwrap()  // panic!
}

// ✅ Use expect when invariant guaranteed
fn get_user(&self) -> &User {
    self.user.as_ref()
        .expect("user always initialized in constructor")
}
```

### Pattern 4: String Handling

```rust
// ✅ Accept &str in APIs
fn greet(name: &str) {
    println!("Hello, {}", name);
}

// ✅ Use Cow when might need owned
use std::borrow::Cow;

fn process(input: &str) -> Cow<str> {
    if input.contains("special") {
        Cow::Owned(input.replace("special", "normal"))
    } else {
        Cow::Borrowed(input)
    }
}

// ✅ Pre-allocate when size known
let mut s = String::with_capacity(100);
```


## Data Type Guidelines

| Rule | Description | Example |
|------|-------------|---------|
| Use newtype | Domain semantics | `struct Email(String)` |
| Use slice patterns | Pattern matching | `if let [first, .., last] = slice` |
| Pre-allocate | Avoid reallocations | `Vec::with_capacity()` |
| Avoid Vec abuse | Fixed size → array | `let arr: [u8; 256]` |

### String Guidelines

| Rule | Description |
|------|-------------|
| ASCII data use `bytes()` | `s.bytes()` faster than `s.chars()` |
| Might modify → `Cow<str>` | Borrow or owned |
| Use `format!` for concat | Better than `+` operator |
| Avoid nested `contains()` | O(n*m) complexity |


## Error Handling Guidelines

| Rule | Description |
|------|-------------|
| Use `?` to propagate | Don't use `try!()` macro |
| `expect()` over `unwrap()` | When value guaranteed |
| Use `assert!` for invariants | At function entry |


## Memory and Lifetimes

| Rule | Description |
|------|-------------|
| Meaningful lifetime names | `'src`, `'ctx` not just `'a` |
| `RefCell` use `try_borrow` | Avoid panics |
| Use shadowing for conversions | `let x = x.parse()?` |


## Concurrency Guidelines

| Rule | Description |
|------|-------------|
| Define lock ordering | Prevent deadlocks |
| Atomics for primitives | Not `Mutex<bool>` |
| Choose memory ordering carefully | Relaxed/Acquire/Release/SeqCst |


## Async Guidelines

| Rule | Description |
|------|-------------|
| CPU-bound → sync | Async for I/O |
| Don't hold locks across await | Use scoped guards |


## Macro Guidelines

| Rule | Description |
|------|-------------|
| Avoid macros (unless necessary) | Prefer functions/generics |
| Macro input like Rust | Readability first |


## Deprecated Patterns → Modern

| Deprecated | Modern | Version |
|-----------|---------|---------|
| `lazy_static!` | `std::sync::OnceLock` | 1.70 |
| `once_cell::Lazy` | `std::sync::LazyLock` | 1.80 |
| `std::sync::mpsc` | `crossbeam::channel` | - |
| `std::sync::Mutex` | `parking_lot::Mutex` | - |
| `failure`/`error-chain` | `thiserror`/`anyhow` | - |
| `try!()` | `?` operator | 2018 |


## Clippy Configuration

```toml
[package]
edition = "2024"
rust-version = "1.85"

[lints.rust]
unsafe_code = "warn"

[lints.clippy]
all = "warn"
pedantic = "warn"
```

### Common Clippy Lints

| Lint | Description |
|------|-------------|
| `clippy::all` | Enable all warnings |
| `clippy::pedantic` | Stricter checks |
| `clippy::unwrap_used` | Avoid unwrap |
| `clippy::expect_used` | Prefer expect |
| `clippy::clone_on_ref_ptr` | Avoid cloning Arc |


## Formatting (rustfmt)

```bash
# Use default config
rustfmt src/lib.rs

# Check formatting
rustfmt --check src/lib.rs

# Config file: .rustfmt.toml
max_width = 100
tab_spaces = 4
edition = "2024"
```


## Documentation Guidelines

```rust
/// Module documentation
//! This module handles user authentication...

/// Struct documentation
///
/// # Examples
/// ```
/// let user = User::new("name");
/// ```
pub struct User { ... }

/// Method documentation
///
/// # Arguments
///
/// * `name` - User name
///
/// # Returns
///
/// Initialized user instance
///
/// # Panics
///
/// Panics when name is empty
pub fn new(name: &str) -> Self { ... }
```


## Workflow

### Step 1: Name Things Properly

```
Choosing a name?
  → Function/variable? snake_case
  → Type/trait? CamelCase
  → Constant? SCREAMING_SNAKE_CASE
  → Conversion method?
    - Cheap view? as_foo()
    - Expensive? to_foo()
    - Consuming? into_foo()
```

### Step 2: Format Code

```bash
# Run rustfmt
cargo fmt

# Check formatting in CI
cargo fmt --check

# Fix clippy warnings
cargo clippy --fix
```

### Step 3: Review Idioms

```
Check:
  → No unnecessary clone()
  → Use ? not unwrap()
  → &str in function parameters
  → Iterator methods not index loops
  → Meaningful error types
```


## Quick Reference

```
Naming: snake_case (fn/var), CamelCase (type), SCREAMING_SNAKE_CASE (const)
Format: rustfmt (just use it)
Docs: /// for public items, //! for module docs
Lint: #![warn(clippy::all)]
```


## Review Checklist

When reviewing code:

- [ ] Naming follows Rust conventions
- [ ] Using `?` instead of `unwrap()`
- [ ] Avoiding unnecessary `clone()`
- [ ] `unsafe` blocks have SAFETY comments
- [ ] Public APIs have doc comments
- [ ] Ran `cargo clippy`
- [ ] Ran `cargo fmt`
- [ ] No `get_` prefix on accessor methods
- [ ] Conversion methods named correctly (as/to/into)
- [ ] String parameters use `&str` when possible


## Verification Commands

```bash
# Format check
cargo fmt --check

# Lint check
cargo clippy -- -D warnings

# Documentation check
cargo doc --no-deps --open

# Run tests
cargo test

# Check naming conventions
cargo clippy -- -W clippy::wrong_self_convention
```


## Common Pitfalls

### 1. Wrong Method Naming

**Symptom**: Clippy warning `wrong_self_convention`

```rust
// ❌ Bad: unnecessary get_ prefix
impl User {
    fn get_name(&self) -> &str { &self.name }
}

// ✅ Good: direct accessor
impl User {
    fn name(&self) -> &str { &self.name }
}
```

### 2. String Type Misuse

**Symptom**: Unnecessary allocations

```rust
// ❌ Bad: forces allocation
fn greet(name: String) {
    println!("Hello, {}", name);
}

// ✅ Good: accepts borrowed or owned
fn greet(name: &str) {
    println!("Hello, {}", name);
}

// Both work now:
greet("Alice");  // &str
greet(&owned_string);  // &String → &str
```

### 3. Index Loops

**Symptom**: Less idiomatic, error-prone

```rust
// ❌ Bad: manual indexing
for i in 0..items.len() {
    println!("{}: {}", i, items[i]);
}

// ✅ Good: iterator
for item in &items {
    println!("{}", item);
}

// ✅ Good: with index
for (i, item) in items.iter().enumerate() {
    println!("{}: {}", i, item);
}
```


## Related Skills

- **rust-anti-pattern** - What not to do
- **rust-error** - Error handling patterns
- **rust-performance** - Performance idioms
- **rust-async** - Async conventions
- **rust-unsafe** - SAFETY comment style


## Localized Reference

- **Chinese version**: [SKILL_ZH.md](./SKILL_ZH.md) - 完整中文版本,包含所有内容

Overview

This skill is a Rust coding conventions expert that helps teams apply idiomatic naming, formatting, linting, and design patterns. It focuses on practical rules for naming, conversions (as_/to_/into_), error handling, memory and lifetime discipline, clippy and rustfmt usage, and documentation. The guidance is actionable and ties rules to common pitfalls and verification commands.

How this skill works

It inspects code and returns concrete suggestions: renaming items to Rust conventions, replacing anti-patterns (unnecessary clones, unwraps, index loops), and recommending conversion method semantics. It also recommends clippy and rustfmt configuration, checks for missing docs and SAFETY comments, and maps deprecated patterns to modern alternatives. Outputs include checklist items, small refactor steps, and CI commands to verify fixes.

When to use it

  • Code reviews to enforce idiomatic Rust naming and APIs
  • Before merging PRs to catch unwraps, unnecessary clones, and wrong accessor names
  • When setting up project-wide tooling: rustfmt, clippy, and CI checks
  • Refactoring sessions to modernize deprecated libraries and idioms
  • Designing public APIs to choose correct ownership and conversion semantics

Best practices

  • Name functions and variables snake_case; types and traits CamelCase; constants SCREAMING_SNAKE_CASE
  • Use as_ for cheap views, to_ for expensive clones, and into_ for consuming conversions
  • Prefer &str in public APIs; use Cow<str> when you may need owned data
  • Propagate errors with ?; use expect only for guaranteed invariants; avoid unwrap in library code
  • Run cargo fmt and cargo clippy in CI; fix clippy warnings and treat important lints as errors
  • Document public items with /// and module-level docs with //!; add examples and panic docs when applicable

Example use cases

  • Rename accessor methods to remove get_ prefix and fix clippy::wrong_self_convention warnings
  • Convert APIs that return owned String to &str parameters and use Cow where conditional allocation is needed
  • Replace lazy_static or once_cell with std synchronization types when bumping Rust version
  • Audit a crate for unnecessary clone() calls and replace with references or owned transfers
  • Add SAFETY comments to unsafe blocks and ensure lifetimes use meaningful names

FAQ

How should I name conversion methods?

Use as_ for cheap borrows, to_ for cloning/allocating conversions, and into_ for consuming ownership transfers.

When is expect acceptable over unwrap?

Use expect when an invariant guaranteed by construction exists; prefer ? or propagate errors in library code to avoid panics.