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_granularity - •
group_imports - •
wrap_comments - •
format_code_in_doc_comments - •
normalize_comments - •
reorder_impl_items - •
brace_style - •
match_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.