Modules and Visibility

Module Trees

Every Rust crate has a single hierarchical module tree rooted at an implicit module named crate, which corresponds to the crate root file (src/lib.rs for a library crate or src/main.rs for a binary crate).

How the Compiler Builds the Module Tree

Unlike Python or Go—where the compiler or runtime automatically discovers .py or .go files in a directory—Rust only compiles files that are explicitly attached to the module tree via a mod name; declaration, starting from src/lib.rs or src/main.rs.

Filesystem Layout vs. Logical Module Tree (`crate`):

  Filesystem (`src/`):                     Logical Module Tree:
  src/
  ├── main.rs        (contains `mod net;   crate
  │                    mod config;`)        ├── net
  ├── config.rs                             │    ├── tcp
  └── net/                                  │    └── tls
      ├── mod.rs     (or `src/net.rs`)      └── config
      ├── tcp.rs
      └── tls.rs

Addressing Items in the Module Tree

Just as directories and files in a filesystem are addressed by / paths, modules and items in the module tree are addressed using :: paths:

Module Tree Concept Filesystem Analogy Rust Syntax
Crate root / (root directory) crate
Current module . (current directory) self
Parent module .. (parent directory) super
Path separator / ::
Attaching a child module Creating a subdirectory entry mod child;

Inspecting a Complete Multi-Level Module Tree

// Crate root (`crate`)
mod config {
    pub const DEFAULT_TIMEOUT_MS: u64 = 1500;
}

mod net {
    pub mod tcp {
        pub fn connect(addr: &str) -> String {
            // Access a sibling subtree (`crate::config`) from `crate::net::tcp`:
            let timeout = crate::config::DEFAULT_TIMEOUT_MS;
            format!("TCP connected to {addr} (timeout = {timeout}ms)")
        }
    }

    pub mod tls {
        pub fn handshake(addr: &str) -> String {
            // Access a sibling module (`super::tcp`) inside `crate::net`:
            let base = super::tcp::connect(addr);
            format!("{base} + TLS 1.3 handshake complete")
        }
    }
}

fn main() {
    let status = crate::net::tls::handshake("10.0.0.1:443");
    println!("{status}");
}
TCP connected to 10.0.0.1:443 (timeout = 1500ms)
TCP connected to 10.0.0.1:443 (timeout = 1500ms) + TLS 1.3 handshake complete

Wait—notice how net::tls::handshake calls super::tcp::connect(addr) once and formats the returned string cleanly:

TCP connected to 10.0.0.1:443 (timeout = 1500ms) + TLS 1.3 handshake complete

Visibility Along a Module Tree Path

For code in module A to refer to an item via a path crate::m1::m2::Item, every segment of the path not already an ancestor of A (m1, m2, and Item) must be visible to A. Even if Item is marked pub, if its enclosing module m2 is private (and not an ancestor of A), A cannot reach m2::Item unless m1 re-exports it with pub use self::m2::Item;.