Important
Format all Rust code using nightly rustfmt
.
More information
cargo +nightly fmt --
Why is this important?
Consistent formatting enhances readability, reduces merge conflicts, and makes code reviews smoother. It ensures that every team member’s code adheres to a unified standard.
Examples & Further Explanation
For instance, a well-formatted codebase allows new team members to quickly understand the project structure and logic. Automated formatting saves time and minimizes stylistic debates during code reviews.
Tip
Use the following .rustfmt.toml
configuration to ensure consistent formatting across the project.
Configuration
# Do not add trailing commas if there is only one element
trailing_comma = "Never"
# Keep braces on the same line where possible
brace_style = "SameLineWhere"
# Align struct fields if their length is below the threshold
struct_field_align_threshold = 20
# Format comments inside documentation
wrap_comments = true
format_code_in_doc_comments = true
# Do not collapse struct literals into a single line
struct_lit_single_line = false
# Maximum line width
max_width = 99
# Grouping imports
imports_granularity = "Crate" # Group imports by crate
group_imports = "StdExternalCrate" # Separate groups: std, external crates, local
reorder_imports = true # Sort imports within groups
# Enable unstable features (nightly only)
unstable_features = true
Why is this important?
This configuration enforces clarity and consistency. It reduces unnecessary diffs in pull requests, makes code reviews easier, and ensures that both style and readability remain predictable across the team.
Examples & Further Explanation
use std::fmt; use std::io; use serde::Serialize;
struct Person {name:String,age:u32}
impl Person{
pub fn new(name:String,age:u32)->Self{
Self{name,age}
}
}
use std::{fmt, io};
use serde::Serialize;
struct Person {
name: String,
age: u32,
}
impl Person {
pub fn new(name: String, age: u32) -> Self {
Self {
name,
age,
}
}
}
Notice how the imports are grouped and sorted, struct fields are aligned for readability, and braces are consistently placed on the same line. This reduces noise in diffs and makes the codebase approachable for both newcomers and experienced contributors.
Important
Use clear, descriptive names that reflect purpose. Follow snake_case for variables/functions, PascalCase for types, and SCREAMING_SNAKE_CASE for constants.
More information
Descriptive Names:
create_user_handler
– OKcreate_user_service
– OKcreate
– NOcreate_user
– NOFollow Rust's snake_case for variables and functions.
Use PascalCase for structs and enums (e.g.,
TransactionStatus
).Constants should be in SCREAMING_SNAKE_CASE.
Why Descriptive Naming?
Descriptive names reduce ambiguity, facilitate easier onboarding, and improve maintainability. Clear names make it evident what a function or variable does, avoiding misunderstandings and conflicts.
Examples & Further Explanation
For example,
create_user_handler
indicates that the function is responsible for handling user creation in a web context, whereas a generic name likecreate
gives no context.
Important
Write clean, maintainable code. Avoid unnecessary complexity, panics, and cloning. Minimize global state and restrict use of ::
to import statements. Do not use mod.rs
files.
More information
- Write clean and maintainable code.
- Avoid unnecessary complexity.
- Avoid unnecessary
unwrap()
andclone()
.- Minimize global state and side effects.
- Use
::
only in import statements.- Do not use
mod.rs
files.Examples & Further Explanation
Instead of writing
some_option.unwrap()
, prefer:let value = some_option.ok_or("Expected a value, but found None")?;
This propagates errors properly and avoids crashing the application. Similarly, favor organizing modules in separate
module_name.rs
files rather than using legacymod.rs
files, which simplifies project structure and improves module discoverability.
Note
Use a professional branching strategy to ensure efficient and conflict-free collaboration.
More information
- Check Existing Branches: Review current active branches to work on the latest relevant code.
- Clarify When in Doubt: If unsure, confirm the correct branch via team chat or project guidelines to avoid conflicts.
- Create a Descriptive Branch: Name branches clearly, e.g.,
feature/123-add-user-authentication
orbugfix/456-fix-crash-on-login
.- Commit and Push Regularly: Commit with clear messages and push frequently to back up work and keep the team informed.
- Create a Pull Request (PR): Open a PR from your branch with clear issue references to provide context for reviewers.
- Clean Up After Merge: Delete merged branches to maintain a tidy repository.
- Minimize Excessive Communication: Use clear branch names, detailed commits, and well-documented PRs to reduce the need for frequent ad hoc discussions.
Real-World Example & Further Explanation
For example, if you start work on a new feature, check existing branches to avoid duplicating work. If uncertain, ask in the team channel or check the project topic. Once confirmed, create a branch like
feature/123-add-user-authentication
, commit changes with detailed messages, and open a PR referencing the issue number. This method reduces unnecessary phone calls and streamlines collaboration.
Tip
Follow best practices to maintain high code quality.
More information
- Use
cargo clippy
for linting.- Handle errors gracefully using
Result
andOption
.- Avoid unnecessary panics.
Examples & Further Explanation
Instead of writing:
let value = some_option.unwrap();
use:
let value = some_option.ok_or("Expected a value, but found None")?;
This pattern ensures errors are propagated and handled appropriately, increasing the robustness of your application.
Important
Avoid panics in production; use proper error handling with Result
and the ?
operator.
More information
- Avoid panics in production code.
- Discouraged: Avoid using
unwrap()
andexpect()
unless absolutely certain that an error cannot occur.- Preferred: Use proper error handling with
Result
and the?
operator.Examples & Further Explanation
For example, instead of:
let config = Config::from_file("config.toml").unwrap();
use:
let config = Config::from_file("config.toml") .map_err(|e| format!("Failed to load config: {}", e))?;
This approach logs detailed error messages and gracefully propagates errors up the call stack, leading to a more robust and maintainable system.
Important
All commits must pass pre-commit checks. Tests, formatting, linting, and security scans are enforced both locally (via pre-commit hooks) and remotely (via CI).
More information
Pre-commit Hooks
- Installed via:
cargo make install-hooks
- Automatically run before each commit:
cargo +nightly fmt --
cargo clippy -D warnings
cargo test --all
- Prevent committing unformatted code, warnings, or failing tests.
Unit Tests
- Cover public functions and error cases.
- Tests must not rely on
unwrap()
orexpect()
.Integration Tests
- Cover public API, placed in the
tests/
directory.Doctests
- All
///
examples must compile and pass withcargo test --doc
.Examples & Further Explanation
#[cfg(test)] mod tests { use super::*; #[test] fn test_basic_math() { assert_eq!(2 + 2, 4); } }// tests/config_tests.rs use my_crate::load_config; #[test] fn load_valid_config() { let result = load_config("tests/data/valid.toml"); assert!(result.is_ok()); }This workflow enforces correctness at every step: developers cannot commit broken code, and CI ensures nothing slips through at merge time.
Following these guidelines ensures that our Rust code is high-quality, maintainable, and scalable.