Code Style¶
Coding conventions for rninja development.
Rust Style¶
Formatting¶
Use rustfmt with default settings:
All code must pass cargo fmt --check in CI.
Linting¶
Code must pass clippy without warnings:
Naming Conventions¶
Follow Rust conventions:
| Item | Convention | Example |
|---|---|---|
| Types | PascalCase | CacheEntry |
| Functions | snake_case | get_cache_key |
| Variables | snake_case | cache_hit |
| Constants | SCREAMING_SNAKE | MAX_CACHE_SIZE |
| Modules | snake_case | cache_manager |
Module Organization¶
// 1. Standard library
use std::collections::HashMap;
use std::path::PathBuf;
// 2. External crates
use serde::{Deserialize, Serialize};
use tokio::fs;
// 3. Internal modules
use crate::cache::CacheKey;
use crate::config::Config;
Error Handling¶
Use thiserror for errors:
use thiserror::Error;
#[derive(Error, Debug)]
pub enum CacheError {
#[error("cache entry not found: {0}")]
NotFound(String),
#[error("cache corrupted: {0}")]
Corrupted(String),
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
}
Use ? for propagation:
fn read_cache(key: &CacheKey) -> Result<Vec<u8>, CacheError> {
let path = get_cache_path(key)?;
let data = std::fs::read(&path)?;
Ok(data)
}
Documentation¶
Document public APIs:
/// Computes the cache key for a build target.
///
/// The key is a BLAKE3 hash of:
/// - Rule name
/// - Command line
/// - Input file contents
///
/// # Arguments
///
/// * `target` - The build target
/// * `inputs` - Input files
///
/// # Returns
///
/// A 32-byte cache key.
///
/// # Examples
///
/// ```
/// let key = compute_cache_key(&target, &inputs)?;
/// assert_eq!(key.len(), 32);
/// ```
pub fn compute_cache_key(
target: &Target,
inputs: &[PathBuf],
) -> Result<CacheKey, CacheError> {
// ...
}
Testing¶
See Testing for full guide.
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cache_hit() {
// Arrange
let cache = Cache::new_test();
let key = CacheKey::from_bytes(b"test");
// Act
cache.put(&key, b"data").unwrap();
let result = cache.get(&key).unwrap();
// Assert
assert_eq!(result, b"data");
}
}
Code Organization¶
File Size¶
- Keep files under 500 lines
- Split large modules into submodules
Function Size¶
- Keep functions under 50 lines
- Extract complex logic into helpers
Struct Design¶
Prefer composition over large structs:
// Good
struct BuildExecutor {
scheduler: Scheduler,
cache: CacheManager,
reporter: ProgressReporter,
}
// Avoid
struct BuildExecutor {
// 20 fields...
}
Performance Guidelines¶
Avoid Allocations in Hot Paths¶
// Good - reuse buffer
fn process_many(items: &[Item], buffer: &mut Vec<u8>) {
for item in items {
buffer.clear();
process_one(item, buffer);
}
}
// Avoid - allocates each iteration
fn process_many(items: &[Item]) {
for item in items {
let buffer = Vec::new();
process_one(item, &buffer);
}
}
Use Appropriate Data Structures¶
// HashSet for O(1) lookups
let built: HashSet<PathBuf> = HashSet::new();
// BTreeMap for sorted iteration
let targets: BTreeMap<String, Target> = BTreeMap::new();
Parallelize with Rayon¶
use rayon::prelude::*;
// Parallel iteration
let results: Vec<_> = inputs
.par_iter()
.map(|input| process(input))
.collect();
Safety Guidelines¶
No Unsafe Without Justification¶
If unsafe is needed, document why:
// SAFETY: We verified that ptr is non-null and properly aligned
// in the check above. The lifetime is tied to self.
unsafe {
&*ptr
}
Handle Errors¶
Never use .unwrap() in library code:
// Good
let value = map.get(&key).ok_or(Error::NotFound)?;
// Avoid in library code
let value = map.get(&key).unwrap();
Validate Input¶
pub fn set_jobs(n: u32) -> Result<(), ConfigError> {
if n == 0 {
return Err(ConfigError::InvalidValue("jobs must be > 0"));
}
// ...
}
Comments¶
When to Comment¶
- Explain "why", not "what"
- Document non-obvious behavior
- Reference issues or design docs
// Good
// Use BTreeMap to ensure deterministic iteration order,
// which is required for reproducible cache keys.
let env_vars: BTreeMap<String, String> = ...
// Avoid
// Create a map
let env_vars: BTreeMap<String, String> = ...
TODO Comments¶
Include issue reference:
Commit Messages¶
Format:
Types: feat, fix, docs, style, refactor, test, chore
Examples:
feat(cache): add LRU eviction policy
Implement LRU eviction when cache exceeds max_size.
Entries are evicted based on last access time.
Closes #45
fix(daemon): handle socket permission errors
Check socket permissions before attempting to connect.
Provides better error message for permission issues.
Fixes #78
Review Checklist¶
Before submitting PR:
-
cargo fmtpasses -
cargo clippyhas no warnings - Tests pass (
cargo test) - New code has tests
- Public APIs documented
- Commit messages follow format