Back to skills
SKILL.md
Rust Coding Standards
ASecurityProduction-ready Rust coding standards enforcing idiomatic patterns, safety, performance, and maintainability. Use when enforcing rust coding conventions and style rules.
- 9 stars
- 0 votes
- 0 copies
- 3 views
- Added September 8, 2026
Works with
Security analysis
100/100npx -y skills add anubhavg-icpl/vibe --skill rust-coding-standards --agent claude-codeAre you the author of Rust Coding Standards?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/anubhavg-icpl-rust-coding-standards)---
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)
Attribution
Comments
Loading comments…
Newsletter
Get the weekly list of safe new skills, and the ones we pulled.