This document outlines the standards and best practices for Rust development at Bayat.
- Follow Rust standard style using
rustfmt
with the default settings - Maximum line length should be 100 characters
- Use 4 spaces for indentation, not tabs
- Files should be encoded in UTF-8
- Files should end with a newline
-
Types and Traits:
- Use PascalCase for types, traits, and enum variants (e.g.,
OrderProcessor
,Serialize
) - Use descriptive names that reflect the purpose or behavior
- Prefer concise but clear names
- Use PascalCase for types, traits, and enum variants (e.g.,
-
Functions and Methods:
- Use snake_case for functions and methods (e.g.,
process_order
,find_by_id
) - Use verbs for functions that perform actions
- Use nouns for functions that return values
- Use snake_case for functions and methods (e.g.,
-
Variables and Parameters:
- Use snake_case for variables and parameters (e.g.,
order_count
,user_name
) - Use descriptive names that reflect the purpose or meaning
- Boolean variables should use
is_
,has_
, orshould_
prefixes (e.g.,is_valid
)
- Use snake_case for variables and parameters (e.g.,
-
Constants and Statics:
- Use SCREAMING_SNAKE_CASE for constants and static variables (e.g.,
MAX_CONNECTIONS
) - Make names descriptive of their purpose
- Use SCREAMING_SNAKE_CASE for constants and static variables (e.g.,
-
Modules:
- Use snake_case for module names (e.g.,
order_processing
,user_management
) - Avoid single-letter module names or overly generic names
- Use snake_case for module names (e.g.,
-
Module Structure:
- Follow the Rust module system conventions
- Use
mod.rs
or separate files based on project complexity - Organize code in modules by functionality or domain
- Maintain a clean hierarchy to enable clear
use
statements
-
File Structure:
- Standard order: imports, module declarations, constants, types, traits, implementations
- Group
use
statements by crate - Place Rust standard library imports first, then external crates, then local imports
- Separate import groups with a blank line
-
Result and Option:
- Use
Result<T, E>
for operations that can fail - Use
Option<T>
for values that may be absent - Prefer using
?
operator for error propagation - Avoid
unwrap()
andexpect()
in production code
- Use
-
Custom Error Types:
- Define domain-specific error types
- Implement the
std::error::Error
trait for custom errors - Use the
thiserror
crate for deriving error implementations - Provide meaningful error messages
-
Error Conversion:
- Implement
From
for converting between error types - Use the
anyhow
crate for flexible error handling in applications - Add context to errors using
.context()
or.with_context()
- Implement
-
Ownership:
- Follow Rust's ownership model strictly
- Prefer passing references over transferring ownership when appropriate
- Use clear lifetimes to express relationships
- Document lifetime requirements in function and type documentation
-
Borrowing:
- Prefer immutable borrows (
&T
) over mutable borrows (&mut T
) when possible - Keep mutable borrows as short and localized as possible
- Avoid nested borrows that could lead to complex lifetime relationships
- Prefer immutable borrows (
-
Smart Pointers:
- Use
Box<T>
for heap allocation with single ownership - Use
Rc<T>
for shared ownership in single-threaded contexts - Use
Arc<T>
for shared ownership in multi-threaded contexts - Use interior mutability patterns (
RefCell
,Mutex
,RwLock
) judiciously
- Use
-
Thread Safety:
- Leverage Rust's type system to ensure thread safety
- Use
Send
andSync
traits appropriately - Prefer message passing over shared state when possible
- Document thread safety guarantees in public APIs
-
Async/Await:
- Use
async
/await
for asynchronous code - Use
tokio
orasync-std
as the async runtime - Avoid mixing futures from different runtimes
- Be mindful of
!Send
futures in async code
- Use
-
Synchronization Primitives:
- Use appropriate synchronization primitives (
Mutex
,RwLock
, etc.) - Prefer
parking_lot
crate equivalents for better performance - Minimize locked sections to reduce contention
- Be aware of potential deadlocks in lock ordering
- Use appropriate synchronization primitives (
-
Generic Code:
- Use generics to create flexible, reusable components
- Constrain generic parameters with trait bounds
- Use
where
clauses for complex trait bounds - Provide type aliases for complex generic types
-
Trait Design:
- Follow the Rust trait design patterns
- Use traits for behavior abstraction
- Design traits around behavior, not data
- Implement standard traits (
Debug
,Display
,Clone
, etc.) when appropriate
-
Trait Objects:
- Use trait objects (
dyn Trait
) for runtime polymorphism - Be aware of the performance implications of dynamic dispatch
- Ensure traits intended for trait objects are object-safe
- Consider alternatives like enum dispatch when performance is critical
- Use trait objects (
-
Test Organization:
- Place tests in a
#[cfg(test)]
module at the bottom of the file - Use nested modules to organize different test categories
- Name test functions descriptively
- Follow the "Arrange, Act, Assert" pattern
- Place tests in a
-
Testing Framework:
- Use Rust's built-in testing framework
- Use
#[test]
attribute for test functions - Use
#[should_panic]
for tests that should panic - Use
assert!
,assert_eq!
, andassert_ne!
macros
-
Test Coverage:
- Aim for high test coverage of business logic
- Test both success and failure paths
- Test edge cases and boundary conditions
- Use test utilities and fixtures to reduce duplication
- Place integration tests in the
tests/
directory - Structure integration tests to test larger components
- Use test fixtures for setting up complex test scenarios
- Mock external services when necessary
- Use
proptest
orquickcheck
for property-based testing - Define properties that should hold for your functions
- Test with a wide range of inputs, including edge cases
- Shrink failing test cases to minimal counterexamples
-
General Principles:
- Optimize for correctness first, then performance
- Measure before optimizing (
criterion
,flamegraph
) - Consider time, memory, and binary size trade-offs
- Document performance characteristics of critical code
-
Memory Optimization:
- Be aware of data structure layouts and sizes
- Use
#[repr(C)]
or#[repr(packed)]
when necessary - Consider custom allocators for specialized needs
- Use stack allocation over heap allocation when possible
-
Algorithmic Optimization:
- Choose appropriate algorithms and data structures
- Use iterators and iterator combinators efficiently
- Leverage zero-cost abstractions
- Avoid redundant allocations
- Use
criterion
for benchmarking - Use
perf
orflamegraph
for profiling - Establish baseline performance metrics
- Create regression tests for performance-critical code
-
Doc Comments:
- Document all public items with
///
or//!
comments - Include examples in documentation
- Document panics, errors, and safety considerations
- Keep documentation up to date with code changes
- Document all public items with
-
Internal Comments:
- Use regular comments (
//
) for implementation details - Comment complex algorithms or non-obvious code
- Avoid redundant comments that repeat the code
- Use
TODO
orFIXME
comments for future work
- Use regular comments (
-
Rustdoc:
- Use Markdown formatting in doc comments
- Group related items with module-level documentation
- Include examples that are tested by
cargo test
- Document unsafe code extensively
-
Evaluation Criteria:
- Prefer mature, well-maintained crates
- Check for recent updates and active maintenance
- Evaluate documentation quality and examples
- Consider license compatibility
-
Versioning:
- Use semantic versioning for dependencies
- Specify version requirements explicitly
- Use caret requirements (
^1.2.3
) for most dependencies - Pin exact versions for critical dependencies
- Use Cargo workspaces for multi-crate projects
- Maintain a clear dependency hierarchy
- Share common dependencies across workspace members
- Use internal crates for code organization
-
Project Setup:
- Use
cargo-edit
for dependency management - Configure appropriate metadata in
Cargo.toml
- Define features for optional functionality
- Set up development and release profiles
- Use
-
CI/CD Integration:
- Run tests on CI for multiple platforms
- Configure linting checks (
clippy
) - Verify formatting with
rustfmt
- Set up test coverage reporting
- Use semantic versioning for releases
- Create release tags in version control
- Generate changelogs for each release
- Automate the release process when possible
-
Clippy:
- Run
clippy
regularly - Enable all clippy lints by default
- Document suppressed lints with reasons
- Update code to address clippy warnings
- Run
-
Rustfmt:
- Use
rustfmt
for consistent formatting - Run
rustfmt
before committing - Use a consistent configuration in
.rustfmt.toml
- Automate formatting in CI
- Use
-
Other Tools:
- Use
cargo-audit
for security vulnerability checking - Use
cargo-deny
for license compliance - Use
cargo-bloat
to identify binary size issues - Consider
cargo-udeps
for unused dependencies
- Use
- Minimize use of unsafe code
- Document why unsafe is necessary
- Encapsulate unsafe code in safe abstractions
- Extensively test unsafe code
- Use
unsafe
blocks as small as possible
- The Rust Programming Language
- Rust API Guidelines
- Rust by Example
- Rust Design Patterns
- Rustonomicon (for unsafe Rust)
Version | Date | Description |
---|---|---|
1.0 | 2025-03-20 | Initial version |