| name | rust-code-organization |
| description | Structure Rust code using modules, traits, builders, and newtypes. Use when designing APIs, abstracting behavior, or preventing type confusion. |
Code Organization
Patterns for structuring maintainable Rust code.
Module Structure
Organize by feature/domain, not by type:
src/
├── lib.rs # Public API exports
├── error.rs # Error types
├── config.rs # Configuration
├── db/
│ ├── mod.rs # Module exports
│ ├── repo.rs # Repository implementation
│ └── models.rs # Database models
├── pipeline/
│ ├── mod.rs
│ ├── processor.rs
│ └── prefetch.rs
└── scheduler/
├── mod.rs
├── manager.rs
└── queue.rs
Module Visibility
mod internal;
pub mod public;
pub(crate) mod shared;
pub(super) mod parent;
pub use self::repo::VideoRepository;
pub use self::models::{VideoRecord, VideoStatus};
Traits for Abstraction
pub trait Repository {
type Record;
type Error;
async fn get(&self, id: &str) -> Result<Self::Record, Self::Error>;
async fn save(&self, record: &Self::Record) -> Result<(), Self::Error>;
}
pub async fn process<R: Repository>(repo: &R, id: &str) -> Result<()> {
let record = repo.get(id).await?;
repo.save(&record).await
}
#[cfg(test)]
struct MockRepository;
#[cfg(test)]
impl Repository for MockRepository {
}
Builder Pattern
pub struct Config {
host: String,
port: u16,
timeout: Duration,
}
#[derive(Default)]
pub struct ConfigBuilder {
host: Option<String>,
port: Option<u16>,
timeout: Option<Duration>,
}
impl ConfigBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn host(mut self, host: impl Into<String>) -> Self {
self.host = Some(host.into());
self
}
pub fn port(mut self, port: u16) -> Self {
self.port = Some(port);
self
}
pub fn timeout(mut self, timeout: Duration) {
.timeout = (timeout);
}
() <Config, ConfigError> {
(Config {
host: .host.(ConfigError::MissingHost)?,
port: .port.(),
timeout: .timeout.(Duration::()),
})
}
}
= ConfigBuilder::()
.()
.()
.()?;
Newtype Pattern
Prevent mixing up types that have the same underlying representation:
fn process_user(user_id: Uuid, order_id: Uuid) { ... }
process_user(order_id, user_id);
pub struct UserId(Uuid);
pub struct OrderId(Uuid);
fn process_user(user_id: UserId, order_id: OrderId) { ... }
process_user(order_id, user_id);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct VideoId(pub i64);
impl VideoId {
pub fn new(id: i64) -> Self {
Self(id)
}
pub fn inner(&self) -> i64 {
self.0
}
}
impl std::fmt::Display for VideoId {
fn fmt(&self, f: &mut std::fmt::Formatter<>) std::fmt:: {
(f, , .)
}
}
Function Composition
fn process_user_data(data: &str) -> Result<User> {
parse_user_data(data)
.and_then(validate_user)
.map(transform_user)
}
fn process_video(path: &Path) -> Result<Video> {
let data = read_file(path)?;
let parsed = parse_video(&data)?;
let validated = validate_video(parsed)?;
Ok(transform_video(validated))
}
Struct Update Syntax
#[derive(Clone)]
pub struct Options {
pub timeout: Duration,
pub retries: u32,
pub verbose: bool,
}
impl Default for Options {
fn default() -> Self {
Self {
timeout: Duration::from_secs(30),
retries: 3,
verbose: false,
}
}
}
let options = Options {
timeout: Duration::from_secs(60),
..Default::default()
};
Extension Traits
pub trait StringExt {
fn truncate_to(&self, max_len: usize) -> &str;
}
impl StringExt for str {
fn truncate_to(&self, max_len: usize) -> &str {
if self.len() <= max_len {
self
} else {
&self[..max_len]
}
}
}
let short = long_string.truncate_to(100);
Guidelines
- Organize modules by domain, not by type
- Use
pub(crate) for internal APIs
- Define traits for testable abstractions
- Use builders for complex configuration
- Use newtypes to prevent type confusion
- Prefer composition over inheritance
- Keep public API surface minimal
Examples
See hercules-local-algo/src/ for production module organization.