Code Organization in Rust
Introduction
Code organization is fundamental to building maintainable software. As projects grow, organizing code into manageable pieces becomes crucial for readability, maintainability, and reusability. Rust provides three main organizational concepts: crates, packages, and modules.
Core Concepts
1. Crates
A crate is Rust's smallest amount of code that the compiler considers at a time. It comes in two forms:
- Binary crates: Programs you can compile to an executable
- Library crates: Code intended to be used by other programs
// Example of a binary crate (src/main.rs)
fn main() {
println!("This is a binary crate");
}
// Example of a library crate (src/lib.rs)
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
2. Packages
A package is a bundle of one or more crates that provides a set of functionality. A package contains a Cargo.toml file that describes how to build those crates.
# Cargo.toml
[package]
name = "my_package"
version = "0.1.0"
edition = "2021"
[dependencies]
serde = "1.0"
Package Rules:
- Can contain at most one library crate
- Can contain any number of binary crates
- Must contain at least one crate (either library or binary)
3. Modules
Modules let you organize code within a crate into groups for readability and easy reuse. They also control the privacy of items.
// Basic module structure
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
fn seat_at_table() {}
}
mod serving {
fn take_order() {}
fn serve_order() {}
fn take_payment() {}
}
}
Module System in Detail
Defining Modules
// In src/lib.rs or src/main.rs
mod authentication {
pub struct Credentials {
pub username: String,
password: String, // private field
}
impl Credentials {
pub fn new(username: String, password: String) -> Credentials {
Credentials {
username,
password,
}
}
pub fn verify(&self) -> bool {
// Implementation details
true
}
}
mod helpers { // private submodule
fn hash_password(password: &str) -> String {
// Implementation details
String::from("hashed_password")
}
}
}
Module File Organization
// src/lib.rs
mod front_of_house; // declares the module
pub use crate::front_of_house::hosting; // re-exports
// src/front_of_house.rs
pub mod hosting {
pub fn add_to_waitlist() {}
}
mod serving {
fn take_order() {}
}
Visibility and Privacy
Public vs Private
mod restaurant {
pub struct Meal {
pub name: String,
price: f64, // private field
}
impl Meal {
pub fn new(name: String, price: f64) -> Meal {
Meal { name, price }
}
pub fn price(&self) -> f64 { // public getter
self.price
}
}
}
Super and Self
mod instruments {
pub mod strings {
pub fn violin() {
super::restring(); // calls parent module's function
self::tune(); // calls function in current module
}
fn tune() {}
}
fn restring() {}
}
Using Modules with use
Basic Use Statements
use std::collections::HashMap;
use std::io::{self, Write};
fn main() {
let mut map = HashMap::new();
io::stdout().flush().unwrap();
}
Advanced Use Patterns
// Re-exporting
pub use crate::front_of_house::hosting;
// Nested paths
use std::{
collections::HashMap,
fs::{self, File},
path::{Path, PathBuf},
};
// Glob operator
use std::collections::*;
Best Practices
1. Module Organization
// Good: Logical grouping
mod users {
mod authentication {
pub fn verify_password() {}
}
mod profiles {
pub fn update_profile() {}
}
// Public interface
pub use authentication::verify_password;
pub use profiles::update_profile;
}
2. File Structure
src/
├── main.rs
├── lib.rs
├── models/
│ ├── mod.rs
│ ├── user.rs
│ └── product.rs
└── utils/
├── mod.rs
└── helpers.rs
3. Re-exports for API Design
// In lib.rs
mod models;
mod utils;
// Public API
pub use models::user::User;
pub use models::product::Product;
pub use utils::helpers::format_currency;
Common Patterns
1. Facade Pattern
// Internal implementation
mod implementation {
pub(crate) fn complex_algorithm() {}
pub(crate) fn another_complex_part() {}
}
// Public API
pub fn simple_interface() {
implementation::complex_algorithm();
implementation::another_complex_part();
}
2. Prelude Pattern
// In lib.rs
pub mod prelude {
pub use crate::models::User;
pub use crate::utils::helpers::*;
}
// In user code
use my_library::prelude::*;
Performance Considerations
-
Module Organization
- Modules are compiled together within a crate
- No runtime overhead for module system
- Module organization affects compile times
-
Use Declarations
- No runtime cost
- Can affect compile-time performance if overused
- Glob imports can increase compile times
Common Pitfalls
1. Privacy Confusion
// Won't work
mod data {
struct User { // private struct
pub name: String, // pub has no effect
}
}
// Fixed version
mod data {
pub struct User { // public struct
pub name: String,
}
}
2. Module Path Issues
// Wrong: Relative path when absolute needed
mod storage {
fn save() {
database::connect(); // Error: can't find database
}
}
// Fixed: Use absolute path
mod storage {
fn save() {
crate::database::connect(); // OK
}
}
Next Steps
After mastering modules:
- Learn about workspace organization for multi-crate projects
- Study dependency management with Cargo
- Explore crate publishing to crates.io
- Understand documentation organization with rustdoc
Remember: Good code organization is crucial for maintainable Rust projects. Take time to plan your module structure, and don't be afraid to refactor as your project evolves.