Rust-skills rust-coding

Rust coding conventions expert covering naming, formatting, comments, clippy, rustfmt, lints, code style, best practices, and idiomatic patterns.

install
source · Clone the upstream repo
git clone https://github.com/huiali/rust-skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/huiali/rust-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/rust-coding" ~/.claude/skills/huiali-rust-skills-rust-coding-95f932 && rm -rf "$T"
manifest: skills/rust-coding/SKILL.md
source content

Naming Conventions (Rust-Specific)

RuleCorrectIncorrect
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

// 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

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

// ✅ 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

// ✅ 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

// ✅ 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

RuleDescriptionExample
Use newtypeDomain semantics
struct Email(String)
Use slice patternsPattern matching
if let [first, .., last] = slice
Pre-allocateAvoid reallocations
Vec::with_capacity()
Avoid Vec abuseFixed size → array
let arr: [u8; 256]

String Guidelines

RuleDescription
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

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

Memory and Lifetimes

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

Concurrency Guidelines

RuleDescription
Define lock orderingPrevent deadlocks
Atomics for primitivesNot
Mutex<bool>
Choose memory ordering carefullyRelaxed/Acquire/Release/SeqCst

Async Guidelines

RuleDescription
CPU-bound → syncAsync for I/O
Don't hold locks across awaitUse scoped guards

Macro Guidelines

RuleDescription
Avoid macros (unless necessary)Prefer functions/generics
Macro input like RustReadability first

Deprecated Patterns → Modern

DeprecatedModernVersion
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

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

[lints.rust]
unsafe_code = "warn"

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

Common Clippy Lints

LintDescription
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)

# 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

/// 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

# 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

# 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

// ❌ 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

// ❌ 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

// ❌ 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 - 完整中文版本,包含所有内容