Rust Style Guide¶
What's on this page
- Core content for this topic in the Inkorporated enterprise OS.
- Practical guidance, roles, or standards as applicable.
What this enables
- Consistent enterprise operations and decision-making.
- Shared language for humans and cyborg AI agents.
This document outlines the coding standards and best practices for Rust development within this project. Adhering to these guidelines ensures code consistency, safety, performance, and maintainability.
Table of Contents¶
- General Principles
- Code Layout
- Naming Conventions
- Documentation
- Types and Traits
- Error Handling
- Ownership and Borrowing
- Concurrency
- Unsafe Code
- Modules and Crates
- Macros
- Testing
- Dependencies
- Recommended Tools
General Principles¶
- Safety: Leverage the type system and borrow checker to ensure memory safety and thread safety. Avoid
unsafeunless absolutely necessary. - Performance: Aim for zero-cost abstractions. Use the most efficient algorithms and data structures suitable for the task.
- Fearless Concurrency: Use Rust's ownership model to write concurrent code without data races.
- Idiomatic Rust: Write "Rustacean" code. Follow community standards and idioms (e.g., using iterators, pattern matching, RAII).
- Clarity: Code should be easy to understand. Explicit is better than implicit.
Code Layout¶
- Formatting: Use
rustfmtwith default settings. Do not manually format code in ways thatrustfmtwould undo. - Indentation: Use 4 spaces for indentation.
- Line Length: Limit lines to 100 characters (default
rustfmtsetting). - Braces: Use K&R style braces (1tbs). Opening brace on the same line, closing brace on its own line.
- Imports:
- Group imports by crate.
- Use
std,external_crate,crate,super,selforder. - Avoid wildcard imports (
use foo::*;) except for preludes or tests.
- Match Arms: Comma-separate match arms. Use braces for multi-line arms.
// Good
use std::collections::HashMap;
use std::sync::Arc;
use serde::{Deserialize, Serialize};
use crate::models::User;
Naming Conventions¶
- Variables/Functions/Modules:
snake_case - Types/Traits/Enum Variants:
UpperCamelCase(PascalCase) - Constants/Statics:
SCREAMING_SNAKE_CASE - Lifetimes:
'short_lowercase(e.g.,'a,'ctx) - Macros:
snake_case! - Getters: Use
field_name()notget_field_name(). - Conversion:
as_type()(borrowed),to_type()(owned/expensive),into_type()(consuming). - Constructors:
new()for the primary constructor.with_params()for others.
struct UserConfig {
max_retries: u32,
}
impl UserConfig {
pub fn new() -> Self { ... }
pub fn max_retries(&self) -> u32 { self.max_retries }
}
Documentation¶
- Doc Comments: Use
///for documenting items (functions, structs, enums) and//!for documenting the containing module/crate. - Format: Use Markdown. The first line should be a concise summary.
- Sections:
# Examples: Show how to use the item. These are run as doctests.# Panics: Document conditions under which the function may panic.# Errors: Document the error conditions and types returned.# Safety: Mandatory forunsafefunctions. Explain the invariants the caller must uphold.
- Completeness: All public items (
pub) must be documented.
/// Divides two numbers.
///
/// # Examples
///
/// ```
/// let result = my_crate::div(10, 2);
/// assert_eq!(result, Some(5));
/// ```
///
/// # Panics
///
/// Panics if `b` is 0.
pub fn div(a: i32, b: i32) -> i32 {
if b == 0 {
panic!("division by zero");
}
a / b
}
Types and Traits¶
- Newtype Pattern: Use tuple structs (
struct UserId(u64);) to enforce type safety and avoid primitives obsession. - Generics vs Trait Objects: Prefer generics (static dispatch) for performance. Use trait objects (
Box<dyn Trait>) when you need dynamic dispatch or heterogeneous collections. - Standard Traits: Implement standard traits like
Debug,Clone,Default,PartialEqwhenever appropriate. Use#[derive(...)]. - From/Into: Implement
From<T>(which gives youInto<T>for free) for fallible type conversions. ImplementTryFromfor fallible conversions. - AsRef/Borrow: Use
AsReffor argument types to accept different representations (e.g.,path: impl AsRef<Path>).
Error Handling¶
- Result: Use
Result<T, E>for recoverable errors. - Option: Use
Option<T>for values that may be absent. - Unwrap/Expect: Avoid
unwrap()in production code. Useexpect("reason")if you are certain it cannot fail (and explaining why helps). In tests,unwrap()is acceptable. - Error Types:
- Question Mark: Use the
?operator for error propagation. - Panics: Only panic for unrecoverable errors (bugs, violated invariants).
use anyhow::{Context, Result};
fn read_config(path: &str) -> Result<String> {
std::fs::read_to_string(path)
.with_context(|| format!("Failed to read config from {}", path))
}
Ownership and Borrowing¶
- Borrowing: Prefer borrowing (
&T) over taking ownership (T) if the function doesn't need to consume the value. - Cloning: Avoid explicit
clone()calls unless necessary. If a type is cheap to copy, implementCopy. - Lifetimes: Elide lifetimes where possible. Use descriptive lifetime names if there are multiple or complex relationships.
- Interior Mutability: Use
CellorRefCellfor single-threaded interior mutability. UseMutexorRwLockfor multi-threaded scenarios.
Concurrency¶
- Send/Sync: Understand
Send(safe to move to another thread) andSync(safe to share between threads). Most types are automatically both. - Async/Await: Use
async/awaitfor I/O-bound tasks. - Runtimes: We prefer
tokioas the standard async runtime. - Channels: Use channels (
tokio::sync::mpsc) for message passing between tasks instead of sharing memory. - Blocking: Never block an async thread. Use
tokio::task::spawn_blockingfor CPU-intensive or blocking synchronous operations.
Unsafe Code¶
- Avoid: Use
unsafeonly when absolutely necessary (e.g., FFI, low-level optimizations verified by benchmarks). - Isolation: Keep
unsafeblocks as small as possible. - Safety Comments: Every
unsafeblock MUST be preceded by a// SAFETY:comment explaining why the operation is safe and how invariants are maintained. - API Safety: Ensure that
unsafeblocks inside a safe function do not expose undefined behavior to the caller.
Modules and Crates¶
- Visibility: Minimize visibility. Use
pub(crate)for items shared within the crate but not exposed publicly. - Prelude: If your library has many commonly used items, consider providing a
preludemodule. - Structure:
- Place unit tests in a
testsmodule within the same file (#[cfg(test)]). - Place integration tests in the
tests/directory at the crate root. - Avoid deep module nesting.
- Place unit tests in a
Macros¶
- Usage: Use macros sparingly. Prefer functions and generics where possible.
- Declarative: Use
macro_rules!for simple pattern matching macros. - Procedural: Use procedural macros (derive, attribute, function-like) for complex code generation, but be mindful of compile times.
Testing¶
- Unit Tests: Write unit tests for all private and public logic. Keep them in the same file as the code.
- Integration Tests: Test the public API in
tests/. - Property-Based Testing: Consider
proptestfor testing a wide range of inputs. - Mocking: Use
mockallfor mocking traits in tests. - Doctests: Ensure examples in documentation compile and pass.
Dependencies¶
- Selection: Choose well-maintained, popular crates (check downloads, recent updates).
- Audit: Use
cargo auditto check for security vulnerabilities. - Features: Disable unused features to reduce compile times and binary size (
default-features = false). - Heavy Dependencies: Be cautious with heavy dependencies like
serde,tokio,syn. Only include what you need.
Recommended Tools¶
- Format:
rustfmt(standard formatter). - Lint:
clippy(catch common mistakes and improve code). Runcargo clippyin CI. - Check:
cargo check(fast compilation check). - Test:
cargo test. - Audit:
cargo audit(security). - Expand:
cargo expand(view macro expansion). - IDE:
rust-analyzer(LSP).