DEV Community

Cover image for Mastering Rust's Module System: Build Better Code Architecture with Privacy and Organization
Aarav Joshi
Aarav Joshi

Posted on

Mastering Rust's Module System: Build Better Code Architecture with Privacy and Organization

As a best-selling author, I invite you to explore my books on Amazon. Don't forget to follow me on Medium and show your support. Thank you! Your support means the world!

Rust's approach to code organization feels different from other languages I've worked with. The module system isn't just about putting code in different files—it's about creating meaningful boundaries that enforce good architectural decisions. When I first encountered it, I appreciated how it made me think about structure from the very beginning of a project.

The basic building block is the module, created with the mod keyword. Modules can contain functions, structs, enums, and even other modules. What struck me was how naturally they map to the problem domain. If I'm building a network application, I might create modules for different protocol implementations.

mod network {
    pub mod tcp {
        pub struct Connection {
            socket: std::net::TcpStream,
            buffer: Vec<u8>,
        }

        impl Connection {
            pub fn new(address: &str) -> Result<Self, std::io::Error> {
                let socket = std::net::TcpStream::connect(address)?;
                Ok(Self {
                    socket,
                    buffer: Vec::with_capacity(1024),
                })
            }
        }
    }

    mod udp {
        // Implementation details for UDP
    }
}
Enter fullscreen mode Exit fullscreen mode

Privacy rules in Rust are strict by default, which I've found prevents many common mistakes. Items are private to their module unless explicitly marked public. This default privacy encourages careful API design—I have to consciously decide what should be exposed.

The pub keyword controls visibility at different levels. I can make something public within the current crate, or public to everyone. There's also pub(crate) for items that should be available throughout the crate but not exported to users.

pub mod analytics {
    pub struct EventTracker {
        events: Vec<Event>,
        config: TrackerConfig,
    }

    pub(crate) fn internal_processor() {
        // Available throughout crate but not exported
    }

    impl EventTracker {
        pub fn new() -> Self {
            Self {
                events: Vec::new(),
                config: TrackerConfig::default(),
            }
        }

        fn validate_event(&self, event: &Event) -> bool {
            // Private method
            true
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

File system organization mirrors the module structure. I can split modules across files while maintaining the logical hierarchy. The mod.rs file serves as the entry point for directory-based modules, declaring what submodules exist and what they export.

src/
  lib.rs
  data/
    mod.rs
    database.rs
    cache.rs
  api/
    mod.rs
    routes.rs
    middleware.rs
Enter fullscreen mode Exit fullscreen mode

In practice, I use the file system modules for larger projects. The mod.rs file typically contains the module declarations and public exports.

// data/mod.rs
pub mod database;
pub mod cache;

pub use database::DatabaseConnection;
pub use cache::CachePool;

// api/mod.rs
pub mod routes;
pub mod middleware;

pub use routes::{create_router, ApiError};
Enter fullscreen mode Exit fullscreen mode

The use keyword brings items into scope, reducing repetition without losing clarity. I can import specific items or entire modules. What I like is that the imports make it clear where things come from, which helps when reading code later.

use data::{DatabaseConnection, CachePool};
use api::create_router;

async fn initialize_app() -> Result<App, Error> {
    let db = DatabaseConnection::new().await?;
    let cache = CachePool::new().await?;
    let router = create_router(db.clone(), cache.clone());

    Ok(App { db, cache, router })
}
Enter fullscreen mode Exit fullscreen mode

Re-exporting with pub use creates cleaner public APIs. I can organize code internally in whatever way makes sense, then present a simplified interface to users. This separation between internal organization and external API is something I value.

// lib.rs
mod internal {
    pub mod processing;
    pub mod validation;
    pub mod storage;
}

pub use internal::processing::Processor;
pub use internal::validation::Validator;
pub use internal::storage::StorageEngine;

// User code gets clean imports
use my_crate::{Processor, Validator, StorageEngine};
Enter fullscreen mode Exit fullscreen mode

Testing integrates beautifully with the module system. I can write tests in the same file as the code they test, or in separate test modules. The privacy rules allow tests to access private items, which means I can test implementation details without exposing them.

mod image_processor {
    pub struct Image {
        pixels: Vec<u8>,
        width: u32,
        height: u32,
    }

    impl Image {
        pub fn new(width: u32, height: u32) -> Self {
            Self {
                pixels: vec![0; (width * height * 4) as usize],
                width,
                height,
            }
        }

        fn validate_coordinates(&self, x: u32, y: u32) -> bool {
            x < self.width && y < self.height
        }
    }

    #[cfg(test)]
    mod tests {
        use super::*;

        #[test]
        fn test_coordinate_validation() {
            let img = Image::new(100, 100);
            assert!(img.validate_coordinates(50, 50));
            assert!(!img.validate_coordinates(150, 50));
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

In larger projects, I often create a tests directory for integration tests. These tests have access only to the public API, which helps ensure I'm testing from the user's perspective.

tests/
  integration_test.rs
  api_test.rs
Enter fullscreen mode Exit fullscreen mode

The module system scales exceptionally well. I've worked on projects with hundreds of modules, and the structure remains clear. Each team can own their set of modules, with well-defined interfaces between them.

What makes Rust's module system stand out is how it works with the language's other features. The privacy rules prevent invalid access at compile time, working alongside the ownership system to ensure memory safety. The result is code that's not just organized well, but fundamentally more robust.

I've found that thinking in terms of modules helps me design better software. It forces me to consider what should be public versus private, what belongs together, and how different parts of the system should interact. This upfront design work pays dividends as the project grows.

The learning curve was there, certainly. Understanding the different visibility modifiers and how they interact took some time. But once it clicked, I found myself wishing other languages had similar systems. The compiler becomes a partner in maintaining good architecture, catching organizational mistakes before they become problems.

Working with Rust's modules has changed how I think about code structure. It's not just about putting code in different files—it's about creating meaningful, enforceable boundaries that make software more maintainable and less error-prone. The system encourages good habits and makes it difficult to take shortcuts that would compromise the architecture.

This approach to organization might seem rigid at first, but I've found it liberating. Instead of worrying about how to structure things, I can focus on the problem domain. The module system provides a framework that guides me toward good decisions, leaving more mental energy for solving the actual problem at hand.

📘 Checkout my latest ebook for free on my channel!

Be sure to like, share, comment, and subscribe to the channel!


101 Books

101 Books is an AI-driven publishing company co-founded by author Aarav Joshi. By leveraging advanced AI technology, we keep our publishing costs incredibly low—some books are priced as low as $4—making quality knowledge accessible to everyone.

Check out our book Golang Clean Code available on Amazon.

Stay tuned for updates and exciting news. When shopping for books, search for Aarav Joshi to find more of our titles. Use the provided link to enjoy special discounts!

Our Creations

Be sure to check out our creations:

Investor Central | Investor Central Spanish | Investor Central German | Smart Living | Epochs & Echoes | Puzzling Mysteries | Hindutva | Elite Dev | Java Elite Dev | Golang Elite Dev | Python Elite Dev | JS Elite Dev | JS Schools


We are on Medium

Tech Koala Insights | Epochs & Echoes World | Investor Central Medium | Puzzling Mysteries Medium | Science & Epochs Medium | Modern Hindutva

Top comments (0)