Modern Rust Expert
Expert knowledge for writing cutting-edge, idiomatic Rust code with Rust edition 2024, strict clippy compliance, and functional-but-pragmatic philosophy.
When to Use This Skill
Automatically apply this knowledge when:
- Writing any Rust code in this project
- Fixing clippy warnings
- Structuring new modules or crates
- Reviewing or refactoring Rust code
Rust Edition 2024 Specifics
Version Requirements
- Edition: 2024
- MSRV: 1.85.0 (minimum for edition 2024)
- Always check
rustc --versionand update withrustup update stableif needed
Edition 2024 & Rust 1.85+ Features Available
1. Async Functions in Traits (Stable!)
Use async fn directly in trait definitions without async-trait crate:
// Modern Rust - async fn in traits (no macro needed!)
trait Database: Send + Sync {
async fn save(&self, data: &[u8]) -> Result<(), Error>;
async fn load(&self, id: &str) -> Result<Vec<u8>, Error>;
}
// Implementation
impl Database for PostgresDb {
async fn save(&self, data: &[u8]) -> Result<(), Error> {
// Direct async implementation
sqlx::query("INSERT INTO ...").execute(&self.pool).await?;
Ok(())
}
}
Use this for: All async trait methods in Environment traits (Database, HttpClient, etc.)
2. Return Position Impl Trait in Traits (RPITIT)
Return impl Trait from trait methods:
trait EventPublisher {
// Can return impl Future instead of Box<dyn Future>
fn publish(&self, event: Event) -> impl Future<Output = Result<()>> + Send;
}
Benefit: No heap allocation, better performance than Box<dyn Future>.
3. Let-Else Statements
Early return with pattern matching:
// Modern pattern for error handling
fn process_order(action: OrderAction) -> Result<Order, Error> {
let OrderAction::PlaceOrder { customer_id, items } = action else {
return Err(Error::InvalidAction);
};
// customer_id and items are in scope here
Ok(Order { customer_id, items })
}
// Compare to old style:
fn process_order_old(action: OrderAction) -> Result<Order, Error> {
match action {
OrderAction::PlaceOrder { customer_id, items } => {
Ok(Order { customer_id, items })
}
_ => Err(Error::InvalidAction),
}
}
Use for: Extracting enum variants with early return.
4. Enhanced Const Generics
Use const generics in more contexts:
// Const generic for effect buffer size
struct EffectBuffer<Action, const N: usize> {
effects: [Option<Effect<Action>>; N],
}
impl<Action, const N: usize> EffectBuffer<Action, N> {
const fn new() -> Self {
Self {
effects: [const { None }; N],
}
}
}
Use for: Stack-allocated buffers with configurable size.
5. Inline Const Expressions
Use const { } blocks in const contexts:
const DEFAULT_CAPACITY: usize = 16;
struct Cache<T> {
// Inline const expression
data: [Option<T>; const { DEFAULT_CAPACITY * 2 }],
}
6. C-String Literals
Create CStr at compile time:
// Modern: c"string" literal
let path = c"/tmp/data"; // Type: &'static CStr
// Old way required:
use std::ffi::CString;
let path = CString::new("/tmp/data").unwrap();
Use for: FFI code and system calls.
7. If/While Let Chains
Combine multiple patterns with &&:
// Modern: let chains
fn handle_event(state: &State, action: Action) {
if let OrderAction::PlaceOrder { items, .. } = action
&& !items.is_empty()
&& state.can_place_order()
{
// All conditions met
}
}
// Old way required nested ifs or match
8. Improved Pattern Matching
Rest patterns in slices:
match events.as_slice() {
[first, .., last] => {
// first and last are available
}
_ => {}
}
Patterns in let statements:
let [first, second, ..] = &events[..] else {
return Err(Error::NotEnoughEvents);
};
9. Expanded Const Fn Capabilities
More operations allowed in const fn:
const fn calculate_capacity(base: usize) -> usize {
// These are all allowed in const fn now:
if base < 16 { 16 } else { base }
}
const fn create_default<T>() -> Option<T> {
None // Can return generic types
}
// Use in const contexts
const CAPACITY: usize = calculate_capacity(10);
10. Precise Capturing in RPIT
Control which lifetimes are captured:
trait Store {
// Only capture 'a, not all lifetimes
fn get_state<'a>(&'a self) -> impl Future<Output = State> + use<'a>;
}
Features NOT Available (Require Nightly)
To avoid confusion, these are NOT in stable yet:
- ❌
genblocks for generators - ❌ Type alias impl trait (TAIT)
- ❌ Specialization
- ❌ Generic const expressions (full support)
- ❌
#[derive]onenumwith generics in some cases
Recommended Patterns for This Project
- Use async fn in traits - No more
async-traitdependency - Prefer let-else - Cleaner error handling
- Use RPITIT - Return
impl Futureinstead ofBox<dyn Future> - Const generics for buffers - Stack allocation where possible
- Let chains - Combine multiple conditions elegantly
Clippy Configuration & Compliance
Workspace Lint Configuration
Always configure lints at workspace level in root Cargo.toml:
[workspace.lints.rust]
unsafe_code = "forbid"
missing_docs = "warn"
[workspace.lints.clippy]
# Pedantic lints - USE LOWER PRIORITY so specific lints can override
pedantic = { level = "warn", priority = -1 }
# Deny common issues
unwrap_used = "deny"
expect_used = "deny"
panic = "deny"
todo = "deny"
unimplemented = "deny"
# Performance
missing_const_for_fn = "warn"
# Cognitive complexity
cognitive_complexity = "warn"
# Documentation
missing_errors_doc = "warn"
missing_panics_doc = "warn"
Critical: Lint groups like pedantic MUST have priority = -1 to avoid conflicts with specific lints.
Individual Crate Configuration
In each crate's Cargo.toml, inherit workspace lints:
[lints]
workspace = true
Common Clippy Issues & Solutions
1. Mixed Attributes Style
Problem: Having both outer (///) and inner (//!) doc comments on the same item.
Wrong:
/// Module for actions
///
/// This contains action types.
pub mod action {
//! Action implementations
}
Correct:
/// Module for actions
///
/// This contains action types.
///
/// Action implementations.
pub mod action {}
Rule: Pick one style per item. For modules, use outer docs (///) and move inner content to the outer doc comment.
2. Documentation Backticks
Rule: ALL type names, function names, trait names in documentation MUST be in backticks.
Wrong:
/// - Database: The database trait
/// - HttpClient: HTTP client
Correct:
/// - `Database`: The database trait
/// - `HttpClient`: HTTP client
3. Missing # Panics Documentation
If a function can panic, document it:
/// Create a test clock
///
/// # Panics
///
/// Panics if the hardcoded timestamp fails to parse
/// (should never happen in practice).
#[allow(clippy::expect_used)] // Justified in test utilities
pub fn test_clock() -> Clock {
// ...
}
4. Non-Debug Types
Some types (like Future) don't implement Debug. Manually implement it:
pub enum Effect<Action> {
Future(Pin<Box<dyn Future<Output = Option<Action>> + Send>>),
}
// Manual Debug implementation
impl<Action> std::fmt::Debug for Effect<Action>
where
Action: std::fmt::Debug,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Effect::Future(_) => write!(f, "Effect::Future(<future>)"),
// ... other arms
}
}
}
5. Missing const fn
If clippy suggests a function can be const, make it const:
// Clippy will suggest this can be const
pub const fn merge(effects: Vec<Effect<Action>>) -> Effect<Action> {
Effect::Parallel(effects)
}
6. Wildcard Imports
Avoid wildcard imports in library code:
Wrong:
use super::*;
Correct:
use super::{Arc, Effect, Reducer, RwLock};
Exception: Wildcard imports are acceptable in tests and examples.
7. Match Arm Consistency
Consolidate identical match arms:
Wrong:
match effect {
Effect::None => {},
Effect::Parallel(_) => {},
Effect::Sequential(_) => {},
}
Correct:
match effect {
Effect::None | Effect::Parallel(_) | Effect::Sequential(_) => {
// Placeholder implementation
},
}
8. Doc List Indentation
Module docs must be indented or separated with blank lines:
Wrong:
pub mod mocks {
//! Mock implementations
}
Correct (moved to outer):
/// Mock implementations
pub mod mocks {}
Rustfmt Configuration
Stable Features Only
Only use rustfmt features available in stable Rust. Many formatting options require nightly.
Safe rustfmt.toml:
edition = "2024"
max_width = 100
hard_tabs = false
tab_spaces = 4
newline_style = "Unix"
fn_params_layout = "Tall"
match_block_trailing_comma = true
chain_width = 60
use_try_shorthand = true
use_field_init_shorthand = true
force_explicit_abi = true
Avoid (require nightly):
imports_granularitygroup_importswrap_commentsformat_code_in_doc_commentsnormalize_commentsreorder_impl_itemsbrace_stylematch_arm_blocks
Functional-but-Pragmatic Philosophy
Core Principles
- Prefer immutability, but allow
&mut selfwhen performance matters - Prefer pure functions, but recognize async/await patterns
- Prefer composition, but allow practical escape hatches
- Favor readability over theoretical purity
Practical Applications
Effect-as-Value Pattern
Describe side effects as values, don't execute them immediately:
// Good: Return effect description
fn reduce(&self, state: &mut State, action: Action, env: &Env) -> Vec<Effect> {
vec![Effect::Database(SaveOrder), Effect::PublishEvent(event)]
}
// Bad: Execute side effects directly
fn reduce(&self, state: &mut State, action: Action, env: &Env) {
env.database.save(state); // ❌ Side effect in reducer!
}
Mutable State in Reducers
It's OK to mutate state in place in reducers for performance:
trait Reducer {
fn reduce(
&self,
state: &mut Self::State, // ✅ Mutable for performance
action: Self::Action,
env: &Self::Environment,
) -> Vec<Effect<Self::Action>>;
}
This is pragmatic: tests are still fast and deterministic, and we avoid unnecessary cloning.
Static Dispatch Over Dynamic
Prefer generic types (static dispatch) over trait objects (dynamic dispatch):
// Prefer this (static dispatch, zero-cost)
struct Store<S, A, E, R>
where
R: Reducer<State = S, Action = A, Environment = E>
{
reducer: R,
}
// Over this (dynamic dispatch, runtime cost)
struct Store<S, A, E> {
reducer: Box<dyn Reducer<State = S, Action = A>>,
}
Exception: Use dynamic dispatch when you need runtime polymorphism (plugins, hot-swapping implementations).
Error Handling
Strict Rules:
- NEVER use
unwrap()orexpect()in library code (deny by clippy) - NEVER use
panic!()in library code (deny by clippy) - NEVER leave
todo!()orunimplemented!()in production code (deny by clippy)
Exceptions (must be explicitly allowed):
// Test utilities can use expect with justification
#[allow(clippy::expect_used)]
pub fn test_clock() -> Clock {
DateTime::parse_from_rfc3339("2025-01-01T00:00:00Z")
.expect("hardcoded timestamp should always parse")
.with_timezone(&Utc)
}
Always document why you're allowing it.
Code Organization Patterns
Module Documentation
Pattern 1: Small modules - Use outer docs only:
/// Action types and utilities
///
/// Actions represent all possible state transitions.
pub mod action {}
Pattern 2: Large modules - No inner docs if you have comprehensive outer docs:
/// Store runtime for coordinating reducer execution
///
/// Detailed explanation here.
///
/// Store runtime implementation details.
pub mod store {
// No //! needed, everything is in outer docs
}
Re-exports
Use pub use for convenience, but document what you're re-exporting:
// Re-export commonly used types from dependencies
pub use chrono::{DateTime, Utc};
pub use serde::{Deserialize, Serialize};
// Re-export from submodules for convenience
pub use mocks::{FixedClock, test_clock};
Workspace Structure
For multi-crate projects:
project/
├── Cargo.toml # Workspace root with [workspace.dependencies]
├── core/
│ ├── Cargo.toml # Uses workspace.dependencies
│ └── src/lib.rs
├── runtime/
│ ├── Cargo.toml # Depends on core
│ └── src/lib.rs
└── testing/
├── Cargo.toml # Depends on core + runtime
└── src/lib.rs
Rules:
- Share dependencies via
[workspace.dependencies] - Use
dependency.workspace = truein crate manifests - Shared package metadata via
[workspace.package] - Workspace lints via
[workspace.lints]
Common Gotchas & Solutions
1. Workspace Dependencies Cannot Be Optional
Wrong:
[workspace.dependencies]
sqlx = { version = "0.8", optional = true } # ❌ Error!
Correct:
[workspace.dependencies]
sqlx = { version = "0.8" } # Define without optional
# In individual crate Cargo.toml:
[dependencies]
sqlx = { workspace = true, optional = true } # ✅ Make it optional here
2. Edition 2024 Requires Newer MSRV
If you get:
error: rust-version 1.83.0 is older than first version (1.85.0) required by edition 2024
Solution: Update MSRV:
[workspace.package]
rust-version = "1.85.0" # Minimum for edition 2024
3. Future Doesn't Implement Debug
Pin<Box<dyn Future>> doesn't implement Debug. See "Non-Debug Types" section above for manual implementation.
4. Async Functions in Placeholders
Placeholders in async functions trigger unused_async:
#[allow(clippy::unused_async)] // Justified: placeholder implementation
async fn execute_effect(&self, effect: Effect<A>) {
// Will be implemented in Phase 1
}
Testing Patterns
Test Organization
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_something() {
// Unit test
}
#[tokio::test]
async fn test_async_something() {
// Async test
}
}
Property-Based Testing
Use proptest for property tests:
use proptest::prelude::*;
proptest! {
#[test]
fn property_holds(input: Vec<u8>) {
prop_assert!(check_property(&input));
}
}
Performance Considerations
Zero-Cost Abstractions
With static dispatch, generics compile to optimal code:
// This generic code:
fn reduce<D: Database>(state: &mut State, db: &D) {
db.save(state);
}
// Monomorphizes to:
fn reduce_with_postgres(state: &mut State, db: &PostgresDatabase) {
db.save(state); // Direct call, no vtable lookup
}
Allocation Minimization
In hot paths, consider SmallVec for small collections:
use smallvec::SmallVec;
// Stack-allocated for ≤ 4 items, heap for more
fn reduce(&self, ...) -> SmallVec<[Effect; 4]> {
let mut effects = SmallVec::new();
effects.push(Effect::Save);
effects
}
Documentation Standards
Crate-Level Documentation
Start lib.rs with comprehensive module docs:
//! # Crate Name
//!
//! Brief description.
//!
//! ## Overview
//!
//! Detailed explanation.
//!
//! ## Example
//!
//! ```ignore
//! // Example code here
//! ```
Function Documentation
/// Brief one-line description
///
/// Longer detailed explanation if needed.
///
/// # Arguments
///
/// - `state`: Description
/// - `action`: Description
///
/// # Returns
///
/// What the function returns.
///
/// # Errors
///
/// When this function returns an error (if applicable).
///
/// # Panics
///
/// When this function panics (if applicable).
///
/// # Example
///
/// ```
/// // Example usage
/// ```
pub fn my_function(state: &State, action: Action) -> Result<(), Error> {
// ...
}
Type Documentation
/// Brief description of the type
///
/// # Type Parameters
///
/// - `S`: State type
/// - `A`: Action type
///
/// # Example
///
/// ```
/// // Example usage
/// ```
pub struct MyType<S, A> {
// ...
}
Quick Reference Checklist
Before committing Rust code:
- [ ] Run
cargo fmt --all - [ ] Run
cargo clippy --all-targets --all-features -- -D warnings - [ ] Run
cargo test --all-features - [ ] Run
cargo doc --no-deps --all-features - [ ] All type names in docs have backticks
- [ ] No
unwrap/panic/todoin library code - [ ] Functions that panic have
# Panicssection - [ ] No wildcard imports (
use super::*) - [ ] Manual
Debugimpl for non-Debug types - [ ] Const fn where applicable
- [ ] Static dispatch preferred over dynamic
Common Commands
# Format code
cargo fmt --all
# Check without building
cargo check --all-features
# Build
cargo build --all-features
# Test
cargo test --all-features
# Lint
cargo clippy --all-targets --all-features -- -D warnings
# Documentation
cargo doc --no-deps --all-features --open
# Documentation with warnings as errors
RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --all-features
Project-Specific Guidelines
This Project's Philosophy
- Functional Core, Imperative Shell: Pure business logic, effects as values
- Explicit over Implicit: All side effects are visible
- Type Safety: Make invalid states unrepresentable
- Testability: Business logic tests run at memory speed
- Performance: Zero-cost abstractions via static dispatch
- Pragmatism: Functional patterns, but practical when needed
Architecture Patterns
- Action: Unified type for commands and events
- Reducer: Pure function
(State, Action, Env) → (State, Effects) - Effect: Values describing side effects (not execution)
- Environment: Trait-based dependency injection
- Store: Runtime that coordinates everything
When in Doubt
- Check the architecture spec:
specs/architecture.md - Follow patterns from existing code
- Prefer explicitness over cleverness
- If clippy complains, there's usually a good reason
- Ask for clarification rather than guessing
Remember: This skill is automatically applied to all Rust code in this project. Follow these guidelines to write idiomatic, performant, and maintainable Rust code.
Scan to join WeChat group