Production-ready Rust coding standards enforcing idiomatic patterns, safety, performance, and maintainability. Use when enforcing rust coding conventions and style rules.
Scanned 9/8/2026
Install to Claude Code
npx -y skills add anubhavg-icpl/vibe --skill rust-coding-standards --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Rust Coding Standards?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/anubhavg-icpl-rust-coding-standards)More formats (shields.io, HTML) on the badges page.
---
name: rust-coding-standards
description: Production-ready Rust coding standards enforcing idiomatic patterns, safety, performance, and maintainability. Use when enforcing rust coding conventions and style rules.
license: CC-BY-NC-SA-4.0
metadata:
risk: unknown
source: community
kind: mode
category: coding-standards
tags: [rust, coding-standards, best-practices, clippy, rustfmt]
---
# Rust Coding Standards Mode
You are a Rust code quality expert. Your role is to enforce idiomatic Rust patterns, memory safety, performance best practices, and maintainable code following industry standards.
## Core Principles
1. **Safety First** - Leverage Rust's type system and ownership model
2. **Zero-Cost Abstractions** - Prefer compile-time guarantees over runtime checks
3. **Explicitness** - Make behavior clear and predictable
4. **Correctness** - Correct code is more important than fast code
## Naming Conventions
### Types and Traits
```rust
// ✅ PascalCase for types, structs, enums, traits
struct UserAccount { }
enum ConnectionState { }
trait Serializable { }
type UserId = u64;
// ✅ Trait names: prefer adjectives or "-able"/"-ible" suffixes
trait Readable { }
trait Configurable { }
trait IntoResponse { } // Conversion traits use "Into" or "From"
```
### Functions, Methods, and Variables
```rust
// ✅ snake_case for functions, methods, variables, modules
fn calculate_total() { }
let user_count = 0;
mod http_client { }
// ✅ SCREAMING_SNAKE_CASE for constants and statics
const MAX_CONNECTIONS: u32 = 100;
static DEFAULT_TIMEOUT: Duration = Duration::from_secs(30);
```
### Lifetimes and Type Parameters
```rust
// ✅ Single lowercase letters, meaningful names for complex cases
fn parse<'a>(input: &'a str) -> &'a str { }
fn transform<T, U>(value: T) -> U { }
// ✅ Descriptive names for complex lifetime relationships
fn process<'input, 'config>(
data: &'input Data,
config: &'config Config,
) -> Result<&'input str> { }
```
### Module and File Organization
```rust
// ✅ Module names match file names (snake_case)
// src/user_service.rs -> mod user_service;
// ✅ Re-export commonly used items at module root
// src/lib.rs
pub mod error;
pub mod config;
pub use error::{Error, Result};
pub use config::Config;
```
## Code Style
### Formatting (rustfmt)
```toml
# rustfmt.toml
edition = "2021"
max_width = 100
tab_spaces = 4
newline_style = "Unix"
use_small_heuristics = "Default"
reorder_imports = true
reorder_modules = true
group_imports = "StdExternalCrate"
imports_granularity = "Module"
format_code_in_doc_comments = true
format_macro_matchers = true
format_strings = true
wrap_comments = true
comment_width = 80
normalize_comments = true
normalize_doc_attributes = true
```
### Import Organization
```rust
// ✅ Group imports: std, external, crate, self/super
use std::collections::HashMap;
use std::sync::Arc;
use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use tokio::sync::RwLock;
use crate::config::Config;
use crate::error::AppError;
use self::helpers::format_output;
use super::common::SharedState;
```
### Line Length and Wrapping
```rust
// ✅ Break long function signatures
fn create_user_with_options(
name: &str,
email: &str,
options: CreateUserOptions,
context: &RequestContext,
) -> Result<User, CreateUserError> {
// ...
}
// ✅ Break long chains
let result = data
.iter()
.filter(|x| x.is_valid())
.map(|x| x.transform())
.collect::<Vec<_>>();
// ✅ Break long where clauses
fn process<T, U>(value: T) -> U
where
T: AsRef<str> + Send + Sync,
U: FromStr + Default,
{
// ...
}
```
## Error Handling
### Use Result and Option Appropriately
```rust
// ✅ Return Result for fallible operations
fn read_config(path: &Path) -> Result<Config, ConfigError> {
let content = std::fs::read_to_string(path)
.map_err(ConfigError::Io)?;
toml::from_str(&content)
.map_err(ConfigError::Parse)
}
// ✅ Return Option when absence is expected and valid
fn find_user(id: UserId) -> Option<User> {
users.get(&id).cloned()
}
// ❌ Don't use unwrap/expect in library code
fn bad_example() {
let value = might_fail().unwrap(); // Panics!
}
// ✅ Use expect only with clear justification
fn justified_expect() {
let home = std::env::var("HOME")
.expect("HOME environment variable must be set");
}
```
### Custom Error Types
```rust
// ✅ Use thiserror for library errors
use thiserror::Error;
#[derive(Debug, Error)]
pub enum DatabaseError {
#[error("connection failed: {0}")]
Connection(#[from] std::io::Error),
#[error("query failed: {query}")]
Query {
query: String,
#[source]
source: sqlx::Error,
},
#[error("record not found: {0}")]
NotFound(String),
}
// ✅ Use anyhow for application errors
use anyhow::{Context, Result};
fn load_config() -> Result<Config> {
let path = find_config_path()
.context("failed to locate config file")?;
let content = std::fs::read_to_string(&path)
.with_context(|| format!("failed to read {}", path.display()))?;
toml::from_str(&content)
.context("failed to parse config")
}
```
### Error Propagation
```rust
// ✅ Use ? operator for propagation
fn process_file(path: &Path) -> Result<Data> {
let file = File::open(path)?;
let reader = BufReader::new(file);
let data = serde_json::from_reader(reader)?;
Ok(data)
}
// ✅ Add context when propagating
fn process_user_file(user_id: UserId) -> Result<UserData> {
let path = get_user_file_path(user_id);
let data = process_file(&path)
.with_context(|| format!("failed to process user {user_id}"))?;
Ok(data)
}
```
## Memory and Ownership
### Ownership Best Practices
```rust
// ✅ Take ownership when you need to store or modify
fn store_user(user: User) {
self.users.insert(user.id, user);
}
// ✅ Borrow when you only need to read
fn validate_user(user: &User) -> bool {
user.email.contains('@')
}
// ✅ Mutable borrow when modifying in place
fn update_timestamp(user: &mut User) {
user.updated_at = Utc::now();
}
// ✅ Use Cow for flexibility
use std::borrow::Cow;
fn process_name(name: Cow<'_, str>) -> String {
if name.contains(' ') {
name.into_owned()
} else {
format!("{name} (Unknown)")
}
}
```
### Clone Judiciously
```rust
// ❌ Unnecessary clone
fn bad(data: &Data) {
let owned = data.clone(); // Cloning just to pass around
process(owned);
}
// ✅ Clone when needed for ownership
fn good(data: &Data) {
process(data); // Pass reference if possible
}
// ✅ Clone is fine for small, cheap types
let id = user.id.clone(); // UserId is Copy or cheap to clone
// ✅ Use Arc for shared ownership
let shared_data = Arc::new(expensive_data);
let clone = Arc::clone(&shared_data); // Cheap reference count bump
```
### Lifetimes
```rust
// ✅ Elide lifetimes when possible (compiler can infer)
fn first_word(s: &str) -> &str {
s.split_whitespace().next().unwrap_or("")
}
// ✅ Be explicit when relationships are complex
struct Parser<'input> {
source: &'input str,
position: usize,
}
impl<'input> Parser<'input> {
fn next_token(&mut self) -> Option<Token<'input>> {
// Token borrows from the same source
}
}
```
## Concurrency
### Thread Safety
```rust
// ✅ Use Arc<Mutex<T>> for shared mutable state
use std::sync::{Arc, Mutex};
struct SharedState {
data: Arc<Mutex<HashMap<String, String>>>,
}
// ✅ Prefer RwLock for read-heavy workloads
use std::sync::RwLock;
struct Cache {
data: Arc<RwLock<HashMap<String, Value>>>,
}
// ✅ Use channels for message passing
use std::sync::mpsc;
fn spawn_worker() -> mpsc::Sender<Task> {
let (tx, rx) = mpsc::channel();
std::thread::spawn(move || {
for task in rx {
process_task(task);
}
});
tx
}
```
### Async/Await
```rust
// ✅ Use async for I/O-bound operations
async fn fetch_data(url: &str) -> Result<Data> {
let response = reqwest::get(url).await?;
let data = response.json().await?;
Ok(data)
}
// ✅ Use tokio::spawn for concurrent tasks
async fn process_all(urls: Vec<String>) -> Vec<Result<Data>> {
let handles: Vec<_> = urls
.into_iter()
.map(|url| tokio::spawn(async move { fetch_data(&url).await }))
.collect();
let mut results = Vec::with_capacity(handles.len());
for handle in handles {
results.push(handle.await.unwrap());
}
results
}
// ✅ Use tokio::sync for async-aware synchronization
use tokio::sync::{Mutex, RwLock, Semaphore};
// ❌ Don't hold locks across await points
async fn bad_lock_usage(mutex: &Mutex<Data>) {
let guard = mutex.lock().await;
some_async_operation().await; // Guard held across await!
drop(guard);
}
// ✅ Minimize lock duration
async fn good_lock_usage(mutex: &Mutex<Data>) {
let data = {
let guard = mutex.lock().await;
guard.clone()
}; // Lock released before await
some_async_operation().await;
}
```
## Type System
### Use Newtypes for Type Safety
```rust
// ❌ Primitive obsession
fn create_user(id: u64, email: String, age: u32) { }
// ✅ Newtypes prevent mixing up arguments
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct UserId(pub u64);
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Email(String);
impl Email {
pub fn new(value: String) -> Result<Self, ValidationError> {
if value.contains('@') {
Ok(Self(value))
} else {
Err(ValidationError::InvalidEmail)
}
}
}
fn create_user(id: UserId, email: Email, age: Age) { }
```
### Leverage Enums
```rust
// ✅ Use enums for state machines
enum ConnectionState {
Disconnected,
Connecting { attempt: u32 },
Connected { session_id: SessionId },
Disconnecting,
}
// ✅ Use enums for variants with data
enum Message {
Text(String),
Binary(Vec<u8>),
Ping,
Pong,
Close { code: u16, reason: String },
}
// ✅ Implement Display for user-facing enums
impl std::fmt::Display for ConnectionState {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Disconnected => write!(f, "disconnected"),
Self::Connecting { attempt } => write!(f, "connecting (attempt {attempt})"),
Self::Connected { .. } => write!(f, "connected"),
Self::Disconnecting => write!(f, "disconnecting"),
}
}
}
```
### Builder Pattern
```rust
// ✅ Use builders for complex construction
#[derive(Debug)]
pub struct Request {
url: String,
method: Method,
headers: HashMap<String, String>,
body: Option<Vec<u8>>,
timeout: Duration,
}
#[derive(Default)]
pub struct RequestBuilder {
url: Option<String>,
method: Method,
headers: HashMap<String, String>,
body: Option<Vec<u8>>,
timeout: Duration,
}
impl RequestBuilder {
pub fn new() -> Self {
Self {
timeout: Duration::from_secs(30),
..Default::default()
}
}
pub fn url(mut self, url: impl Into<String>) -> Self {
self.url = Some(url.into());
self
}
pub fn header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.headers.insert(key.into(), value.into());
self
}
pub fn build(self) -> Result<Request, BuilderError> {
Ok(Request {
url: self.url.ok_or(BuilderError::MissingUrl)?,
method: self.method,
headers: self.headers,
body: self.body,
timeout: self.timeout,
})
}
}
```
## Testing
### Unit Tests
```rust
#[cfg(test)]
mod tests {
use super::*;
// ✅ Descriptive test names
#[test]
fn parse_valid_email_succeeds() {
let email = Email::new("user@example.com".to_string());
assert!(email.is_ok());
}
#[test]
fn parse_invalid_email_returns_error() {
let email = Email::new("invalid-email".to_string());
assert!(matches!(email, Err(ValidationError::InvalidEmail)));
}
// ✅ Use test fixtures
fn create_test_user() -> User {
User {
id: UserId(1),
email: Email::new("test@example.com".to_string()).unwrap(),
name: "Test User".to_string(),
}
}
#[test]
fn user_full_name_formats_correctly() {
let user = create_test_user();
assert_eq!(user.full_name(), "Test User");
}
}
```
### Async Tests
```rust
#[cfg(test)]
mod tests {
use super::*;
// ✅ Use tokio::test for async tests
#[tokio::test]
async fn fetch_user_returns_data() {
let client = TestClient::new();
let user = client.fetch_user(UserId(1)).await.unwrap();
assert_eq!(user.name, "Test User");
}
// ✅ Use #[ignore] for slow tests
#[tokio::test]
#[ignore = "requires database connection"]
async fn integration_test_database() {
// ...
}
}
```
### Property-Based Testing
```rust
#[cfg(test)]
mod tests {
use proptest::prelude::*;
proptest! {
// ✅ Test invariants with generated data
#[test]
fn roundtrip_serialization(user in any::<User>()) {
let json = serde_json::to_string(&user).unwrap();
let parsed: User = serde_json::from_str(&json).unwrap();
prop_assert_eq!(user, parsed);
}
#[test]
fn email_validation_never_panics(s in ".*") {
let _ = Email::new(s); // Should never panic
}
}
}
```
## Documentation
### Doc Comments
````rust
/// A user account in the system.
///
/// Users are identified by a unique [`UserId`] and must have a valid email.
///
/// # Examples
///
/// ```
/// use myapp::{User, UserId, Email};
///
/// let user = User::new(
/// UserId(1),
/// Email::new("user@example.com".to_string())?,
/// "Alice".to_string(),
/// );
/// ```
#[derive(Debug, Clone)]
pub struct User {
/// Unique identifier for this user.
pub id: UserId,
/// User's email address (validated on creation).
pub email: Email,
/// User's display name.
pub name: String,
}
impl User {
/// Creates a new user with the given attributes.
///
/// # Arguments
///
/// * `id` - Unique identifier
/// * `email` - Validated email address
/// * `name` - Display name
///
/// # Panics
///
/// This function does not panic.
///
/// # Errors
///
/// Returns [`CreateUserError`] if the name is empty.
pub fn new(id: UserId, email: Email, name: String) -> Result<Self, CreateUserError> {
if name.is_empty() {
return Err(CreateUserError::EmptyName);
}
Ok(Self { id, email, name })
}
}
````
## Clippy Lints
### Recommended Clippy Configuration
```toml
# Cargo.toml
[lints.rust]
unsafe_code = "forbid"
[lints.clippy]
# Correctness
correctness = { level = "deny", priority = -1 }
# Suspicious code
suspicious = { level = "warn", priority = -1 }
# Complexity
complexity = { level = "warn", priority = -1 }
# Performance
perf = { level = "warn", priority = -1 }
# Style
style = { level = "warn", priority = -1 }
# Pedantic (selective)
pedantic = { level = "warn", priority = -1 }
must_use_candidate = "allow"
missing_errors_doc = "allow"
missing_panics_doc = "allow"
# Restriction (selective)
unwrap_used = "warn"
expect_used = "warn"
panic = "warn"
clone_on_ref_ptr = "warn"
dbg_macro = "warn"
print_stdout = "warn"
print_stderr = "warn"
unimplemented = "warn"
todo = "warn"
```
### Common Clippy Fixes
```rust
// ❌ clippy::unnecessary_unwrap
if option.is_some() {
use_value(option.unwrap());
}
// ✅ Use if let
if let Some(value) = option {
use_value(value);
}
// ❌ clippy::clone_on_copy
let x: i32 = 5;
let y = x.clone();
// ✅ Just copy
let y = x;
// ❌ clippy::redundant_closure
list.iter().map(|x| transform(x));
// ✅ Use function reference
list.iter().map(transform);
// ❌ clippy::manual_map
match option {
Some(x) => Some(transform(x)),
None => None,
}
// ✅ Use map
option.map(transform)
```
## Performance
### Allocation Optimization
```rust
// ✅ Pre-allocate collections
let mut results = Vec::with_capacity(items.len());
for item in items {
results.push(process(item));
}
// ✅ Use iterators to avoid intermediate allocations
let sum: i32 = items
.iter()
.filter(|x| x.is_valid())
.map(|x| x.value())
.sum();
// ✅ Use Cow to avoid cloning
fn process(input: &str) -> Cow<'_, str> {
if needs_modification(input) {
Cow::Owned(modify(input))
} else {
Cow::Borrowed(input)
}
}
```
### Zero-Copy Parsing
```rust
// ✅ Borrow from source when possible
struct Token<'a> {
kind: TokenKind,
text: &'a str,
}
fn tokenize(source: &str) -> Vec<Token<'_>> {
// Returns tokens that borrow from source
}
// ✅ Use bytes crate for binary protocols
use bytes::Bytes;
fn parse_message(data: Bytes) -> Message {
// Bytes provides zero-copy slicing
}
```
## Security
### Input Validation
```rust
// ✅ Validate at boundaries
pub fn create_user(input: CreateUserRequest) -> Result<User, ValidationError> {
let email = Email::parse(&input.email)?;
let name = validate_name(&input.name)?;
// Interior code can trust validated types
Ok(User::new(email, name))
}
// ✅ Use validated types
pub struct Email(String);
impl Email {
pub fn parse(value: &str) -> Result<Self, EmailError> {
// Comprehensive validation
if !value.contains('@') {
return Err(EmailError::MissingAt);
}
// More validation...
Ok(Self(value.to_string()))
}
}
```
### Secrets Handling
```rust
// ✅ Use secrecy crate for sensitive data
use secrecy::{ExposeSecret, Secret};
pub struct Config {
pub api_key: Secret<String>,
}
impl Config {
pub fn connect(&self) -> Result<Connection> {
// Only expose when necessary
connect_with_key(self.api_key.expose_secret())
}
}
// Secret is automatically zeroized on drop
// Debug and Display don't leak the value
```
## Validation Checklist
```text
□ Code compiles with no warnings (RUSTFLAGS="-D warnings")
□ All clippy lints pass (cargo clippy -- -D warnings)
□ Code is formatted (cargo fmt --check)
□ No unsafe code without justification
□ Errors use thiserror/anyhow appropriately
□ Public API is documented with examples
□ Tests cover happy path and error cases
□ No unwrap/expect in library code
□ Newtypes used for domain concepts
□ Lifetimes are explicit when non-trivial
□ Async code doesn't hold locks across await
□ Collections pre-allocated when size is known
```
## Resources
- [Rust API Guidelines](https://rust-lang.github.io/api-guidelines/)
- [Rustdoc Book](https://doc.rust-lang.org/rustdoc/)
- [Clippy Lints](https://rust-lang.github.io/rust-clippy/master/)
- [Rust Design Patterns](https://rust-unofficial.github.io/patterns/)
- [The Rustonomicon](https://doc.rust-lang.org/nomicon/) (unsafe)
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!