Code Style
π¨ Coding standards and style guidelines for ReasonKit contributors.
ReasonKit is written in Rust and follows strict code quality standards. This guide helps you write code that fits seamlessly into the codebase.
Core Philosophy
- Clarity over cleverness β Readable code wins
- Explicit over implicit β Donβt hide behavior
- Fail fast, fail loud β No silent failures
- Performance matters β But not at the cost of correctness
Rust Style Guide
Formatting
We use rustfmt with project-specific settings. Always run before committing:
cargo fmt
Configuration (.rustfmt.toml):
edition = "2021"
max_width = 100
tab_spaces = 4
use_small_heuristics = "Default"
Naming Conventions
| Item | Convention | Example |
|---|---|---|
| Types/Traits | PascalCase | ThinkTool, ReasoningProfile |
| Functions/Methods | snake_case | run_analysis(), get_config() |
| Variables | snake_case | user_input, analysis_result |
| Constants | SCREAMING_SNAKE | DEFAULT_TIMEOUT, MAX_RETRIES |
| Modules | snake_case | thinktool, retrieval |
| Feature flags | kebab-case | embeddings-local |
Error Handling
Use the crateβs error types consistently:
#![allow(unused)]
fn main() {
use crate::error::{ReasonKitError, Result};
// Good: Use ? operator with context
fn process_input(input: &str) -> Result<Analysis> {
let parsed = parse_input(input)
.map_err(|e| ReasonKitError::Parse(format!("Invalid input: {}", e)))?;
analyze(parsed)
}
// Bad: Unwrap in library code
fn process_input_bad(input: &str) -> Analysis {
parse_input(input).unwrap() // Don't do this!
}
}
Documentation
Every public item must have documentation:
#![allow(unused)]
fn main() {
/// Executes the GigaThink reasoning module.
///
/// Generates multiple perspectives on a problem by exploring
/// it from different viewpoints, stakeholders, and frames.
///
/// # Arguments
///
/// * `input` - The question or problem to analyze
/// * `config` - GigaThink configuration options
///
/// # Returns
///
/// A `GigaThinkResult` containing all generated perspectives
/// and a synthesis of the analysis.
///
/// # Errors
///
/// Returns `ReasonKitError::Provider` if the LLM call fails.
///
/// # Example
///
/// ```rust
/// use reasonkit::thinktool::{gigathink, GigaThinkConfig};
///
/// let config = GigaThinkConfig::default();
/// let result = gigathink("Should I switch jobs?", &config)?;
/// println!("Found {} perspectives", result.perspectives.len());
/// ```
pub fn gigathink(input: &str, config: &GigaThinkConfig) -> Result<GigaThinkResult> {
// implementation
}
}
Module Organization
#![allow(unused)]
fn main() {
// mod.rs structure
//
// 1. Module documentation
// 2. Re-exports (pub use)
// 3. Public types
// 4. Private types
// 5. Public functions
// 6. Private functions
// 7. Tests
//! ThinkTool execution module.
//!
//! This module provides the core reasoning tools that power ReasonKit.
pub use self::executor::Executor;
pub use self::profiles::{Profile, ProfileConfig};
mod executor;
mod profiles;
mod registry;
/// Main entry point for ThinkTool execution.
pub fn run(input: &str, profile: &Profile) -> Result<Analysis> {
let executor = Executor::new(profile)?;
executor.run(input)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_run_with_default_profile() {
// test implementation
}
}
}
Imports
Organize imports in this order:
#![allow(unused)]
fn main() {
// 1. Standard library
use std::collections::HashMap;
use std::path::PathBuf;
// 2. External crates
use serde::{Deserialize, Serialize};
use tokio::sync::mpsc;
// 3. Internal crates (workspace members)
use reasonkit_db::VectorStore;
// 4. Crate modules
use crate::error::Result;
use crate::thinktool::Profile;
// 5. Super/self
use super::Config;
}
Async Code
ReasonKit uses Tokio for async operations:
#![allow(unused)]
fn main() {
// Good: Use async properly
pub async fn call_llm(prompt: &str) -> Result<String> {
let client = Client::new();
let response = client
.post(&api_url)
.json(&request)
.send()
.await
.map_err(|e| ReasonKitError::Provider(e.to_string()))?;
response.text().await
.map_err(|e| ReasonKitError::Parse(e.to_string()))
}
// Good: Spawn tasks when parallelism helps
pub async fn run_tools_parallel(
input: &str,
tools: &[Tool],
) -> Result<Vec<ToolResult>> {
let handles: Vec<_> = tools
.iter()
.map(|tool| {
let input = input.to_string();
let tool = tool.clone();
tokio::spawn(async move { tool.run(&input).await })
})
.collect();
futures::future::try_join_all(handles)
.await
.map_err(|e| ReasonKitError::Internal(e.to_string()))
}
}
Linting
All code must pass Clippy with no warnings:
cargo clippy -- -D warnings
Common Clippy fixes:
#![allow(unused)]
fn main() {
// Bad: Unnecessary clone
let s = some_string.clone();
do_something(&s);
// Good: Borrow instead
do_something(&some_string);
// Bad: Redundant pattern matching
match result {
Ok(v) => Some(v),
Err(_) => None,
}
// Good: Use .ok()
result.ok()
}
Performance Guidelines
Avoid Allocations in Hot Paths
#![allow(unused)]
fn main() {
// Bad: Allocates on every call
fn format_error(code: u32) -> String {
format!("Error code: {}", code)
}
// Good: Return static str when possible
fn error_message(code: u32) -> &'static str {
match code {
1 => "Invalid input",
2 => "Timeout",
_ => "Unknown error",
}
}
}
Use Iterators Over Vectors
#![allow(unused)]
fn main() {
// Bad: Creates intermediate vector
let results: Vec<_> = items.iter()
.filter(|x| x.is_valid())
.collect();
let sum: u32 = results.iter().map(|x| x.value).sum();
// Good: Chain iterator operations
let sum: u32 = items.iter()
.filter(|x| x.is_valid())
.map(|x| x.value)
.sum();
}
Testing Requirements
See Testing Guide for full details. Quick summary:
- Unit tests for all public functions
- Integration tests for cross-module behavior
- Benchmarks for performance-critical code
Pre-Commit Checklist
Before every commit:
# Format code
cargo fmt
# Run linter
cargo clippy -- -D warnings
# Run tests
cargo test
# Check docs compile
cargo doc --no-deps
Related
- Pull Requests β PR submission guidelines
- Testing β Testing requirements
- Architecture β System design