rust programming

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

  1. Module Organization

    • Modules are compiled together within a crate
    • No runtime overhead for module system
    • Module organization affects compile times
  2. 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.