# Code Style & Conventions ## Rust Style Configuration The project uses **rustfmt** with a custom configuration (`rustfmt.toml`): ### Key Formatting Rules - **Edition**: 2021 - **Braces**: `PreferSameLine` for structs/enums, `AlwaysSameLine` for control flow - **Function Layout**: `Tall` (each parameter on its own line for long signatures) - **Single-line Functions**: Disabled (`fn_single_line = false`) - **Imports**: - Grouping: `StdExternalCrate` (std, external, then local) - Layout: `Vertical` (one import per line) - Granularity: `Crate` level - Reorder: Enabled - **Comments**: - Width: 80 characters - Wrapping: Enabled - Format code in doc comments: Enabled - **Doc Attributes**: Normalized (`normalize_doc_attributes = true`) - **Impl Items**: Reordered (`reorder_impl_items = true`) - **Match Arms**: Leading pipes always shown - **Hex Literals**: Lowercase ### Applying Formatting ```bash # Format all code cargo fmt # Check without modifying cargo fmt -- --check ``` ## Naming Conventions ### Rust Standard Conventions - **Types** (structs, enums, traits): `PascalCase` - Example: `EngineBridge`, `PersistenceConfig`, `SessionId` - **Functions & Methods**: `snake_case` - Example: `run_executor()`, `get_database_path()` - **Constants**: `SCREAMING_SNAKE_CASE` - Example: `APP_NAME`, `DEFAULT_BUFFER_SIZE` - **Variables**: `snake_case` - Example: `engine_bridge`, `db_path_str` - **Modules**: `snake_case` - Example: `debug_ui`, `engine_bridge` - **Crates**: `kebab-case` in Cargo.toml, `snake_case` in code - Example: `sync-macros` → `sync_macros` ### Project-Specific Patterns - **Platform modules**: `platform/desktop/`, `platform/ios/` - **Plugin naming**: Suffix with `Plugin` (e.g., `EngineBridgePlugin`, `CameraPlugin`) - **Resource naming**: Prefix with purpose (e.g., `PersistenceConfig`, `SessionManager`) - **System naming**: Suffix with `_system` for Bevy systems - **Bridge pattern**: Use `Bridge` suffix for inter-component communication (e.g., `EngineBridge`) ## Code Organization ### Module Structure ```rust // Public API first pub mod engine; pub mod networking; pub mod persistence; // Internal modules mod debug_ui; mod platform; // Re-exports for convenience pub use engine::{EngineCore, EngineBridge}; ``` ### Import Organization ```rust // Standard library use std::sync::Arc; use std::thread; // External crates (grouped by crate) use bevy::prelude::*; use serde::{Deserialize, Serialize}; use tokio::runtime::Runtime; // Internal crates use libmarathon::engine::EngineCore; use libmarathon::platform; // Local modules use crate::camera::*; use crate::debug_ui::DebugUiPlugin; ``` ## Documentation ### Doc Comments - Use `///` for public items - Use `//!` for module-level documentation - Include examples where helpful - Document panics, errors, and safety considerations ```rust /// Creates a new engine bridge for communication between Bevy and EngineCore. /// /// # Returns /// /// A tuple of `(EngineBridge, EngineHandle)` where the bridge goes to Bevy /// and the handle goes to EngineCore. /// /// # Examples /// /// ```no_run /// let (bridge, handle) = EngineBridge::new(); /// app.insert_resource(bridge); /// // spawn EngineCore with handle /// ``` pub fn new() -> (EngineBridge, EngineHandle) { // ... } ``` ### Code Comments - Keep line comments at 80 characters or less - Explain *why*, not *what* (code should be self-documenting for the "what") - Use `// TODO:` for temporary code that needs improvement - Use `// SAFETY:` before unsafe blocks to explain invariants ## Error Handling ### Library Code (libmarathon) - Use `thiserror` for custom error types - Return `Result` from fallible functions - Provide context with error chains ```rust use thiserror::Error; #[derive(Error, Debug)] pub enum EngineError { #[error("failed to connect to peer: {0}")] ConnectionFailed(String), #[error("database error: {0}")] Database(#[from] rusqlite::Error), } ``` ### Application Code (app) - Use `anyhow::Result` for application-level error handling - Add context with `.context()` or `.with_context()` ```rust use anyhow::{Context, Result}; fn load_config() -> Result { let path = get_config_path() .context("failed to determine config path")?; std::fs::read_to_string(&path) .with_context(|| format!("failed to read config from {:?}", path))? // ... } ``` ## Async/Await Style ### Tokio Runtime Usage - Spawn blocking tasks in background threads - Use `tokio::spawn` for async tasks - Prefer `async fn` over `impl Future` ```rust // Good: Clear async function async fn process_events(&mut self) -> Result<()> { // ... } // Background task spawning std::thread::spawn(move || { let rt = tokio::runtime::Runtime::new().unwrap(); rt.block_on(async { core.run().await; }); }); ``` ## Testing Conventions ### Test Organization - Unit tests: In same file as code (`#[cfg(test)] mod tests`) - Integration tests: In `tests/` directory - Benchmarks: In `benches/` directory ### Test Naming - Use descriptive names: `test_sync_between_two_nodes` - Use `should_` prefix for behavior tests: `should_reject_invalid_input` ```rust #[cfg(test)] mod tests { use super::*; #[test] fn test_engine_bridge_creation() { let (bridge, handle) = EngineBridge::new(); // ... } #[tokio::test] async fn should_sync_state_across_peers() { // ... } } ``` ## Platform-Specific Code ### Feature Gates ```rust // iOS-specific code #[cfg(target_os = "ios")] use tracing_oslog::OsLogger; // Desktop-specific code #[cfg(not(target_os = "ios"))] use tracing_subscriber::fmt; ``` ### Platform Modules - Keep platform-specific code in `platform/` modules - Provide platform-agnostic interfaces when possible - Use feature flags: `desktop`, `ios`, `headless` ## Logging ### Use Structured Logging ```rust use tracing::{debug, info, warn, error}; // Good: Structured with context info!(path = %db_path, "opening database"); debug!(count = peers.len(), "connected to peers"); // Avoid: Plain string info!("Database opened at {}", db_path); ``` ### Log Levels - `error!`: System failures requiring immediate attention - `warn!`: Unexpected conditions that are handled - `info!`: Important state changes and milestones - `debug!`: Detailed diagnostic information - `trace!`: Very verbose, rarely needed ## RFCs and Design Documentation ### When to Write an RFC - Architectural decisions affecting multiple parts - Choosing between significantly different approaches - Introducing new protocols or APIs - Making breaking changes ### RFC Structure (see `docs/rfcs/README.md`) - Narrative-first explanation - Trade-offs and alternatives - API examples (not full implementations) - Open questions - Success criteria ## Git Commit Messages ### Format ``` Brief summary (50 chars or less) More detailed explanation if needed. Wrap at 72 characters. - Use bullet points for multiple changes - Reference issue numbers: #123 Explains trade-offs, alternatives considered, and why this approach was chosen. ``` ### Examples ``` Add CRDT synchronization over iroh-gossip Implements the protocol described in RFC 0001. Uses vector clocks for causal ordering and merkle trees for efficient reconciliation. - Add VectorClock type - Implement GossipBridge for peer communication - Add integration tests for two-peer sync ```