| name | rust-style-conventions |
| description | Foundational Rust style conventions that apply universally to all Rust code. Covers import organization, naming conventions, and code formatting standards. This skill is automatically applied to all Rust implementations and should be referenced by all other Rust skills. |
| always_apply | true |
Rust Style Conventions
Universal Rust style conventions for consistent, readable production code
Applicability
This skill applies automatically to ALL Rust code. All other Rust skills (rust-core-patterns, axum-web-framework, rust-async-runtime, etc.) inherit these conventions.
Import Style (MANDATORY)
All use imports MUST be at the top of files. Never use inline paths.
The Rule
Import types and functions at the top of the file, then use the imported names in code.
Incorrect (Inline Paths)
fn main() {
let url = std::env::var("DATABASE_URL")?;
let duration = std::time::Duration::from_secs(5);
let path = std::path::PathBuf::from("/tmp");
let pending = std::future::pending::<()>();
}
Correct (Top-Level Imports)
use std::env;
use std::future;
use std::path::PathBuf;
use std::time::Duration;
fn main() {
let url = env::var("DATABASE_URL")?;
let duration = Duration::from_secs(5);
let path = PathBuf::from("/tmp");
let pending = future::pending::<()>();
}
Why This Matters
- Readability: All dependencies visible at file top
- Refactoring: Easier to track and update imports
- Conflict detection: Namespace conflicts visible immediately
- IDE support: Better autocomplete and navigation
- Consistency: Matches Rust community conventions
Use What You Import (CRITICAL)
If a name is imported, USE the imported name. Never mix imported names with fully-qualified paths in the same file.
use std::env::var;
fn get_config() -> String {
std::env::var("KEY").unwrap()
}
use std::env::var;
fn get_config() -> String {
var("KEY").unwrap()
}
This applies to ALL imports — if you write use X, then use X, never the full path.
use std::time::Duration;
let short = Duration::from_secs(1);
let long = std::time::Duration::from_secs(60);
use std::time::Duration;
let short = Duration::from_secs(1);
let long = Duration::from_secs(60);
Enforcement: If you see a fully-qualified path in code, check the imports. If it's already imported, use the imported name.
Import Organization
Group imports in this order with blank lines between groups:
use std::collections::HashMap;
use std::sync::Arc;
use std::time::Duration;
use axum::{Router, routing::get};
use serde::{Deserialize, Serialize};
use tokio::sync::mpsc;
use my_domain::User;
use my_utils::helpers;
use crate::config::Config;
use crate::error::AppError;
Partial Imports
Prefer importing the module rather than every function:
use std::env;
use std::fs;
let value = env::var("KEY")?;
let content = fs::read_to_string("file.txt")?;
use std::time::Duration;
use std::sync::Arc;
let timeout = Duration::from_secs(30);
let shared = Arc::new(data);
Nested Imports
Use nested imports for multiple items from the same module:
use axum::{
extract::{Path, Query, State},
routing::{get, post},
Router,
};
use axum::extract::Path;
use axum::extract::Query;
use axum::extract::State;
use axum::routing::get;
use axum::routing::post;
use axum::Router;
Naming Conventions
Follow standard Rust naming conventions:
| Item | Convention | Example |
|---|
| Crates | snake_case | my_crate |
| Modules | snake_case | user_service |
| Types (struct, enum, trait) | PascalCase | UserService |
| Functions | snake_case | get_user |
| Methods | snake_case | find_by_id |
| Local variables | snake_case | user_count |
| Constants | SCREAMING_SNAKE_CASE | MAX_CONNECTIONS |
| Statics | SCREAMING_SNAKE_CASE | DEFAULT_TIMEOUT |
| Type parameters | PascalCase, single letter preferred | T, E, R |
| Lifetimes | lowercase, short | 'a, 'de |
Formatting Standards
Use rustfmt
All code must pass cargo fmt --check. Use default rustfmt settings unless project specifies otherwise.
Line Length
- Target 100 characters maximum
- Break long function signatures and chains
Expression Breaking
let result = collection
.iter()
.filter(|x| x.is_valid())
.map(|x| x.process())
.collect::<Vec<_>>();
let response = client
.post(&url)
.header("Authorization", token)
.json(&payload)
.send()
.await?;
Documentation
Public API Documentation
All public items should have doc comments:
pub fn create_user(email: &str, name: &str) -> Result<User, CreateUserError> {
}
Module Documentation
Add module-level documentation:
mod create;
mod delete;
mod update;
Error Messages
Write clear, actionable error messages:
#[error("user not found with id {id}")]
NotFound { id: UserId },
#[error("email {email} is already registered")]
EmailExists { email: String },
#[error("error occurred")]
GenericError,
#[error("invalid")]
Invalid,
Best Practices Summary
- Imports at top: Never use inline paths like
std::time::Duration
- Use what you import: If
use std::env::var; exists, use var() not std::env::var()
- Organize imports: Group by std, external, workspace, local
- Follow naming conventions: PascalCase for types, snake_case for functions
- Run rustfmt: All code must be formatted
- Document public APIs: Use
/// doc comments
- Clear error messages: Be specific and actionable
Integration with Other Skills
This skill provides foundational conventions used by:
- rust-core-patterns: Type definitions follow naming conventions
- axum-web-framework: Handler functions follow import style
- rust-async-runtime: Tokio imports organized properly
- rust-error-handling: Error messages follow guidelines
- rust-observability: Tracing macros with proper imports
- axum-service-architecture: Module organization follows conventions
- rust-testing-verification: Test code follows same standards
- rust-production-reliability: All production code formatted
- maud-syntax-fundamentals: Template functions follow style
- maud-axum-integration: Integration code follows conventions
- maud-components-patterns: Component functions follow naming
References