Rust Development
D5.0This skill enforces new code or code changes to conform to proper Rust guidelines.
Get This Skill on GitHubOverview
name: rust-development description: Expert Rust development guidance covering naming conventions, type safety, error handling, documentation, and best practices based on official Rust guidelines. license: Apache-2.0 tags:
- rust
- development
- best-practices
- coding-standards
- type-safety
Rust Development
This skill enforces new code or code changes to conform to proper Rust guidelines.
When to Use
Activate this skill when:
- Writing new Rust code
- Reviewing or modifying existing Rust code
- Designing Rust APIs, libraries, or applications
- Refactoring Rust codebases
Required before submitting code:
- All code must pass
cargo clippy -- -D warnings - All code must be formatted with
cargo +nightly fmt
Guidelines Sources
These guidelines are based on:
- Microsoft Rust Guidelines
- Rust API Guidelines
- The Rust Programming Language Book
- Rust community best practices
Naming Conventions (RFC 430)
Case Rules
| Item | Convention | Example |
|---|---|---|
| Modules | snake_case | my_module |
| Types & Traits | UpperCamelCase | MyStruct, MyTrait |
| Enum variants | UpperCamelCase | Some, None |
| Functions & Methods | snake_case | do_something |
| Macros | snake_case! | my_macro! |
| Statics & Constants | SCREAMING_SNAKE_CASE | MAX_SIZE |
| Type parameters | Single uppercase letter | T, U, E |
| Lifetimes | Single lowercase letter with ' | 'a, 'b |
Important Rules
- Acronyms count as one word: use
Uuid, notUUID;HttpRequest, notHTTPRequest - In
snake_case, single-letter words only appear last:btree_map, notb_tree_map - Avoid
-rsor-rustsuffixes in crate names - Feature names should be direct: use
std, notuse-std
Conversion Methods
| Prefix | Cost | Ownership | Example |
|---|---|---|---|
as_ | Free | borrowed → borrowed | as_str(), as_bytes() |
to_ | Expensive | borrowed → owned | to_string(), to_vec() |
into_ | Variable | owned → owned (non-Copy) | into_inner(), into_vec() |
The mut qualifier goes where it would in the return type: as_mut_slice(), not as_slice_mut().
Getter Methods
- Omit the
get_prefix: usefirst(), notget_first() - Reserve
getfor single, obvious values likeCell::get
Iterator Methods
Collections should provide:
fn iter(&self) -> Iter<'_, T> // borrowed iteration
fn iter_mut(&mut self) -> IterMut<'_, T> // mutable iteration
fn into_iter(self) -> IntoIter<T> // owned iteration
Iterator type names must match their producing methods.
Type Safety
Use Newtypes for Distinctions
Create wrapper types to distinguish values with the same underlying type:
// Bad: easy to confuse
fn set_dimensions(width: u32, height: u32) { }
// Good: compiler catches mistakes
struct Width(u32);
struct Height(u32);
fn set_dimensions(width: Width, height: Height) { }
Avoid Primitive Obsession
Use custom types instead of bool, u8, or Option when meaning is unclear:
// Bad: what do these bools mean?
Widget::new(true, false)
// Good: self-documenting
enum Size { Small, Large }
enum Shape { Round, Square }
Widget::new(Size::Small, Shape::Round)
Bitflags for Multiple Flags
Use the bitflags crate for independent flags:
use bitflags::bitflags;
bitflags! {
struct Permissions: u32 {
const READ = 0b001;
const WRITE = 0b010;
const EXECUTE = 0b100;
}
}
Builders for Complex Construction
For types with many optional parameters:
struct Config {
timeout: Duration,
retries: u32,
// ...
}
struct ConfigBuilder { /* ... */ }
impl ConfigBuilder {
fn new() -> Self { /* ... */ }
fn timeout(mut self, timeout: Duration) -> Self { /* ... */ }
fn retries(mut self, retries: u32) -> Self { /* ... */ }
fn build(self) -> Result<Config, Error> { /* ... */ }
}
Trait Implementations
Common Traits to Implement
Types should eagerly implement applicable standard traits:
| Trait | When to Implement |
|---|---|
Clone | Almost always |
Copy | For small, trivially copyable types |
Debug | Always for public types |
Default | When a sensible default exists |
PartialEq, Eq | For comparable types |
PartialOrd, Ord | For orderable types |
Hash | For types used as map keys |
Display | For user-facing output |
Send, Sync | When thread-safe |
Conversion Traits
// Implement From for infallible conversions
impl From<&str> for MyString { }
// Implement TryFrom for fallible conversions
impl TryFrom<&str> for MyValidatedString {
type Error = ValidationError;
}
// Implement AsRef/AsMut for borrowing
impl AsRef<str> for MyString { }
// Never implement Into/TryInto directly - they're derived automatically
Collection Traits
Collections should implement:
FromIterator- enablescollect()Extend- enablesextend()IntoIterator- enablesforloops
Serialization
Implement Serde traits behind a feature flag:
[features]
serde = ["dep:serde"]
[dependencies]
serde = { version = "1", features = ["derive"], optional = true }
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct MyType { }
Object Safety for Trait Objects
If a trait may be useful as a trait object (dyn Trait), ensure it's object-safe:
// Object-safe: can be used as dyn Trait
trait Drawable {
fn draw(&self);
fn bounds(&self) -> Rect;
}
// NOT object-safe: has generic method
trait Processor {
fn process<T>(&self, item: T); // Generics not allowed
}
// NOT object-safe: returns Self
trait Clonable {
fn clone(&self) -> Self; // Self by value not allowed
}
Only Smart Pointers Implement Deref
Only implement Deref and DerefMut for smart pointer types:
// Good: Smart pointer implementing Deref
struct MyBox<T>(T);
impl<T> Deref for MyBox<T> {
type Target = T;
fn deref(&self) -> &T { &self.0 }
}
// Bad: Non-pointer type implementing Deref
struct Email(String);
impl Deref for Email {
type Target = str; // Don't do this - use AsRef instead
fn deref(&self) -> &str { &self.0 }
}
Use AsRef instead for non-pointer types that provide access to inner data.
Debug Output Should Never Be Empty
Debug implementations should always produce meaningful output:
// Bad: Empty debug output
impl fmt::Debug for MyType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Ok(()) // Produces nothing
}
}
// Good: At minimum show type name
impl fmt::Debug for MyType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("MyType").finish()
}
}
Error Handling
Error Type Requirements
Error types must:
- Implement
std::error::Error - Implement
Send + Sync - Implement
Displaywith lowercase message, no trailing punctuation - Never use
()as an error type
use std::error::Error;
use std::fmt;
#[derive(Debug)]
pub struct MyError {
kind: ErrorKind,
source: Option<Box<dyn Error + Send + Sync>>,
}
impl fmt::Display for MyError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "operation failed: {}", self.kind) // lowercase, no period
}
}
impl Error for MyError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
self.source.as_ref().map(|e| e.as_ref() as _)
}
}
Use thiserror for Library Errors
use thiserror::Error;
#[derive(Debug, Error)]
pub enum MyError {
#[error("failed to read file: {path}")]
ReadFile { path: String, source: std::io::Error },
#[error("invalid configuration")]
InvalidConfig,
}
Use anyhow for Application Errors
use anyhow::{Context, Result};
fn main() -> Result<()> {
let config = read_config()
.context("failed to read configuration")?;
Ok(())
}
Propagate with ?, Not unwrap()
// Bad
let file = File::open(path).unwrap();
// Good
let file = File::open(path)?;
// Good with context
let file = File::open(path)
.with_context(|| format!("failed to open {}", path.display()))?;
Use let ... else for Early Returns
Extract values and exit early to keep the happy path unindented:
// Bad: Nested conditionals
fn process(value: Option<String>) -> Result<(), Error> {
if let Some(s) = value {
if !s.is_empty() {
// happy path buried in nesting
do_work(&s)?;
}
}
Ok(())
}
// Good: let-else keeps happy path flat
fn process(value: Option<String>) -> Result<(), Error> {
let Some(s) = value else {
return Ok(());
};
if s.is_empty() {
return Ok(());
}
do_work(&s)?;
Ok(())
}
Benefits:
- Keeps the main logic at the top level of indentation
- Makes early exit conditions explicit
- Reduces rightward drift in complex functions
Documentation
Requirements
- Every public item must have documentation
- Crate-level docs (
//!in lib.rs) should provide overview and examples - Include examples that compile and run
Function Documentation Sections
/// Brief description of what this function does.
///
/// More detailed explanation if needed.
///
/// # Arguments
///
/// * `arg1` - Description of first argument
///
/// # Returns
///
/// Description of return value.
///
/// # Errors
///
/// Returns `Err` if:
/// - condition one occurs
/// - condition two occurs
///
/// # Panics
///
/// Panics if:
/// - some invariant is violated
///
/// # Safety
///
/// This function is unsafe because:
/// - caller must ensure X
///
/// # Examples
///
/// ```
/// use my_crate::my_function;
///
/// let result = my_function(42)?;
/// assert_eq!(result, 84);
/// # Ok::<(), my_crate::Error>(())
/// ```
pub fn my_function(arg1: i32) -> Result<i32, Error> { }
Example Guidelines
- Use
?for error handling, notunwrap()ortry! - Hide boilerplate with
#prefix - Ensure examples compile with
cargo test --doc
Hide Implementation Details
Use #[doc(hidden)] to hide internal items from documentation:
// Public but hidden from docs
#[doc(hidden)]
pub mod __internal {
// Implementation details needed by macros
}
// Hide re-exports of internal types
#[doc(hidden)]
pub use crate::internal::Helper;
Don't expose helper types, internal modules, or implementation details in public documentation.
Memory and Safety
Minimize unsafe
- Avoid
unsafewhen safe alternatives exist - Isolate
unsafecode in small, well-documented functions - Document all safety invariants
/// Returns a reference to the element at `index`.
///
/// # Safety
///
/// Caller must ensure `index < self.len()`.
pub unsafe fn get_unchecked(&self, index: usize) -> &T {
// SAFETY: caller guarantees index is in bounds
unsafe { self.data.get_unchecked(index) }
}
Prefer Owned Types for Thread Safety
- Return owned types when the data might be used across threads
- Document
SendandSyncbounds explicitly
Avoid Interior Mutability Unless Necessary
- Prefer
&mut selfmethods overCell/RefCell - When using interior mutability, document why it's needed
Performance
Avoid Unnecessary Allocations
// Bad: allocates unnecessarily
fn greet(name: &str) -> String {
format!("Hello, {}!", name.to_string())
}
// Good: reuses the input
fn greet(name: &str) -> String {
format!("Hello, {name}!")
}
Use Iterators Over Indexing
// Bad: bounds checking on each access
for i in 0..vec.len() {
process(vec[i]);
}
// Good: optimized iteration
for item in &vec {
process(item);
}
Prefer &str Over String in Parameters
// Bad: forces caller to allocate
fn process(s: String) { }
// Good: accepts both String and &str
fn process(s: &str) { }
// Good: generic over string types
fn process(s: impl AsRef<str>) { }
Use Cow for Conditional Ownership
use std::borrow::Cow;
fn process(input: &str) -> Cow<'_, str> {
if needs_modification(input) {
Cow::Owned(modify(input))
} else {
Cow::Borrowed(input)
}
}
API Design
Functions Should Not Take Out-Parameters
// Bad: out-parameter
fn get_values(output: &mut Vec<i32>) { }
// Good: return the value
fn get_values() -> Vec<i32> { }
Expose Intermediate Results
Allow callers to avoid duplicate work:
// Bad: forces re-parsing
fn is_valid(s: &str) -> bool { }
fn parse(s: &str) -> Result<Value, Error> { }
// Good: parse once, check validity from result
fn parse(s: &str) -> Result<Value, Error> { }
// Caller can check is_ok() for validity
Constructors Are Static Methods
impl MyType {
// Primary constructor
pub fn new() -> Self { }
// Alternative constructors
pub fn with_capacity(cap: usize) -> Self { }
pub fn from_parts(a: A, b: B) -> Self { }
}
Smart Pointers Don't Add Inherent Methods
Types implementing Deref should not add methods that might conflict with the target type.
Use Generics to Minimize Assumptions
Accept generic parameters instead of concrete types when possible:
// Bad: Requires specific type
fn read_config(file: File) -> Config { }
// Good: Accepts any reader
fn read_config(reader: impl Read) -> Config { }
// Good: Accepts any string-like type
fn greet(name: impl AsRef<str>) {
println!("Hello, {}", name.as_ref());
}
Validate Function Arguments
Functions should validate their inputs rather than trusting callers:
// Bad: Trusts caller to validate
fn get_element(slice: &[u8], index: usize) -> u8 {
slice[index] // Panics on invalid index
}
// Good: Returns Option for invalid input
fn get_element(slice: &[u8], index: usize) -> Option<u8> {
slice.get(index).copied()
}
// Good: Use types that enforce validity
fn process_nonempty(items: NonEmpty<Item>) { }
Project Structure
Cargo.toml Metadata
[package]
name = "my-crate"
version = "0.1.0"
edition = "2021"
rust-version = "1.70" # MSRV
authors = ["Author Name <email@example.com>"]
description = "Brief description of the crate"
documentation = "https://docs.rs/my-crate"
repository = "https://github.com/user/my-crate"
license = "MIT OR Apache-2.0"
keywords = ["keyword1", "keyword2"]
categories = ["category1"]
[dependencies]
# Use specific versions or version ranges
serde = { version = "1.0", features = ["derive"] }
[dev-dependencies]
# Test-only dependencies
Module Organization
src/
├── lib.rs # Public API, re-exports
├── error.rs # Error types
├── types.rs # Core types
├── internal/ # Private implementation
│ ├── mod.rs
│ └── helpers.rs
└── tests/ # Integration test helpers
Feature Flags
- Use feature flags for optional functionality
- Name features clearly without placeholder words
- Document features in Cargo.toml and README
[features]
default = []
serde = ["dep:serde"]
async = ["dep:tokio"]
Testing
Test Organization
// Unit tests in the same file
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_basic_functionality() {
// ...
}
}
// Integration tests in tests/ directory
// tests/integration_test.rs
Test Naming
#[test]
fn function_name_condition_expected_result() {
// e.g., parse_valid_input_returns_value
}
Property-Based Testing
Consider using proptest or quickcheck for property-based tests:
use proptest::prelude::*;
proptest! {
#[test]
fn roundtrip_serialization(value: MyType) {
let serialized = serialize(&value);
let deserialized = deserialize(&serialized)?;
prop_assert_eq!(value, deserialized);
}
}
Future Proofing
Use Private Fields
Structs should have private fields to allow future changes without breaking users:
// Bad: Public fields lock in representation
pub struct Point {
pub x: f64,
pub y: f64,
}
// Good: Private fields with accessors
pub struct Point {
x: f64,
y: f64,
}
impl Point {
pub fn new(x: f64, y: f64) -> Self { Self { x, y } }
pub fn x(&self) -> f64 { self.x }
pub fn y(&self) -> f64 { self.y }
}
Sealed Traits
Use sealed traits to prevent downstream implementations while allowing use:
mod private {
pub trait Sealed {}
}
/// This trait is sealed and cannot be implemented outside this crate.
pub trait MyTrait: private::Sealed {
fn method(&self);
}
// Only types in this crate can implement MyTrait
impl private::Sealed for MyType {}
impl MyTrait for MyType {
fn method(&self) { }
}
Dependability
Destructors Must Not Fail
Drop implementations must not panic or fail:
impl Drop for Resource {
fn drop(&mut self) {
// Bad: Can panic
self.file.flush().unwrap();
// Good: Log error but don't panic
if let Err(e) = self.file.flush() {
eprintln!("warning: failed to flush: {e}");
}
}
}
If cleanup can fail, provide an explicit close() method that returns Result.
Clippy and Formatting
Required Clippy Lints
Enable these in lib.rs or main.rs:
#![warn(
clippy::all,
clippy::pedantic,
clippy::nursery,
missing_docs,
rust_2018_idioms,
)]
#![allow(
clippy::module_name_repetitions, // if needed
)]
Rustfmt Configuration
Create rustfmt.toml:
edition = "2021"
max_width = 100
use_small_heuristics = "Default"
Required: Run Before Committing
All code must pass these checks before being submitted:
# Format code (required)
cargo +nightly fmt
# Check for lint errors (required - must pass with no warnings)
cargo clippy -- -D warnings
# Run tests
cargo test
# Verify docs build
cargo doc --no-deps
Code that fails clippy or is not properly formatted should not be committed.
Checklist for Code Review
Required
- Code passes
cargo clippy -- -D warnings - Code is formatted with
cargo +nightly fmt - Tests pass with
cargo test
Naming
- Follows RFC 430 case conventions
- Conversions use
as_/to_/into_correctly - No
get_prefix on simple getters - Iterator methods follow conventions
Types
- Uses newtypes for distinct concepts
- Avoids bool/Option parameters with unclear meaning
- Implements appropriate standard traits
- Error types implement
Error + Send + Sync
Safety
- Minimizes
unsafecode - Documents safety invariants
- Validates inputs at boundaries
Documentation
- All public items documented
- Examples compile and demonstrate usage
- Error/panic conditions documented
Performance
- Avoids unnecessary allocations
- Uses iterators appropriately
- Takes references where ownership isn't needed
Testing
- Unit tests for core logic
- Integration tests for public API
- Edge cases covered
What This Skill Can Do
AI-generated examples showing real capabilities
Ready to use this skill?
Visit the original repository to get the full skill configuration and installation instructions.
View on GitHub