Modules and Visibility

Separating Code Across Files

When a module grows too large for an inline mod name { ... } block, replacing the brace body with a semicolon (mod name;) tells rustc to load the module's contents from a separate .rs source file on disk.

Where rustc Looks for mod name;

When mod network; appears in the crate root (src/main.rs or src/lib.rs), the compiler searches for the module file in two standard locations:

  1. Modern style (Rust 2018+ / 2024 recommended): src/network.rs (with any child submodules of network placed inside src/network/<child>.rs).
  2. Legacy directory style: src/network/mod.rs (with any child submodules placed alongside it in src/network/<child>.rs).
Declaration Location Statement Modern File Path (Preferred) Legacy File Path
src/main.rs or src/lib.rs mod network; src/network.rs src/network/mod.rs
src/network.rs (or src/network/mod.rs) pub mod tcp; src/network/tcp.rs src/network/tcp/mod.rs
src/network/tcp.rs pub mod frame; src/network/tcp/frame.rs src/network/tcp/frame/mod.rs

Important: Never create both src/network.rs and src/network/mod.rs in the same project. If both exist, rustc rejects the build with an ambiguity error (E0761: file for module 'network' found at both "src/network.rs" and "src/network/mod.rs").

Multi-File Project Walkthrough

Project Directory Layout (Modern Style):

  my_project/
  ├── Cargo.toml
  └── src/
      ├── main.rs            <-- Declares `mod auth;` and `mod server;`
      ├── server.rs          <-- Defines `crate::server`
      ├── auth.rs            <-- Declares `pub mod token;` and defines `crate::auth`
      └── auth/
          └── token.rs       <-- Defines `crate::auth::token`

Notice that inside src/server.rs or src/auth/token.rs, you do not write mod server { ... } or mod token { ... }—the file itself is already the body of that module!

// --- File: src/auth/token.rs ---
// pub fn verify(raw: &str) -> bool {
//     raw == "secret-2024"
// }

// --- File: src/auth.rs ---
// pub mod token; // Loads `src/auth/token.rs`
//
// pub fn login(key: &str) -> Result<&'static str, &'static str> {
//     if token::verify(key) {
//         Ok("Session established")
//     } else {
//         Err("Invalid token")
//     }
// }

// --- Equivalent Single-Binary Demonstration ---
mod auth {
    pub mod token {
        pub fn verify(raw: &str) -> bool {
            raw == "secret-2024"
        }
    }

    pub fn login(key: &str) -> Result<&'static str, &'static str> {
        if token::verify(key) {
            Ok("Session established")
        } else {
            Err("Invalid token")
        }
    }
}

fn main() {
    println!("{:?}", auth::login("secret-2024"));
    println!("{:?}", auth::login("wrong"));
}
Ok("Session established")
Err("Invalid token")

Overriding the File Path with #[path = "..."]

In rare cases—such as platform-specific module implementations (#[cfg(unix)] vs. #[cfg(windows)]) or generated code—you can override the default filename lookup using the #[path = "..."] attribute:

#[cfg(unix)]
#[path = "sys/unix.rs"]
mod sys;

#[cfg(windows)]
#[path = "sys/windows.rs"]
mod sys;