Modules and Visibility

Defining Modules

A module is declared in Rust using the mod keyword. Every crate starts with an implicit root module named crate (formed by src/lib.rs or src/main.rs), and all submodules are attached to this tree using either inline module blocks (mod name { ... }) or external module declarations (mod name;).

Two Ways to Define a Module

Declaration Syntax Where the Module's Code Lives When to Use
Inline Module (mod name { ... }) Directly inside the curly braces { ... } in the current file Small helper modules, unit test modules (#[cfg(test)] mod tests { ... }), or self-contained examples
File Module (mod name;) In a separate file (src/name.rs or src/name/mod.rs) Multi-file projects where each module has its own source file
Option A — Inline in `src/main.rs`:         Option B — External File via `mod math;`:
  mod math {                                  // In `src/main.rs`:
      pub fn add(a: i32, b: i32) -> i32 {     mod math;
          a + b
      }                                       // In `src/math.rs`:
  }                                           pub fn add(a: i32, b: i32) -> i32 {
                                                  a + b
                                              }
  (Both produce the EXACT same module path: `crate::math::add`!)

Defining Inline Modules (mod name { ... })

An inline module groups items (fn, struct, enum, const, trait, and nested mod blocks) inside curly braces. Module names follow snake_case:

mod geometry {
    pub struct Circle {
        pub radius: f64,
    }

    pub fn area(circle: &Circle) -> f64 {
        std::f64::consts::PI * circle.radius * circle.radius
    }

    // Private helper function (no `pub`): callable ONLY inside `geometry`!
    fn is_valid_radius(r: f64) -> bool {
        r >= 0.0
    }

    pub fn new_circle(radius: f64) -> Option<Circle> {
        if is_valid_radius(radius) {
            Some(Circle { radius })
        } else {
            None
        }
    }
}

fn main() {
    if let Some(c) = geometry::new_circle(3.0) {
        println!("Circle radius = {}, area = {:.2}", c.radius, geometry::area(&c));
    }
}
Circle radius = 3, area = 28.27

Visibility Rules Between Parent and Child Modules

When you nest modules inside one another, visibility follows an asymmetric rule similar to directory permissions:

  • Children can see everything in their ancestor modules: A child module (outer::inner) has automatic access to all items—even private items—defined in its parent module (outer) and ancestor modules (crate) via super:: or crate::.
  • Parents CANNOT see private items inside their child modules: A parent module (outer) can only access items inside its child module (inner) if both the child item (and any intermediate modules) are marked pub!
+-------------------------------------------------------------------+
| mod outer {                                                       |
|     fn private_parent_helper() {}   <-- Visible to `inner`!       |
|                                                                   |
|     pub mod inner {                                               |
|         pub fn public_child_fn() {                                |
|             super::private_parent_helper(); // ✅ Allowed!        |
|         }                                                         |
|         fn secret_child_fn() {}     <-- ❌ Invisible to `outer`!  |
|     }                                                             |
| }                                                                 |
+-------------------------------------------------------------------+
mod outer {
    const PARENT_SECRET: i32 = 42;

    pub mod inner {
        pub fn read_parent_secret() -> i32 {
            // Child module can access private items in its parent (`super::`):
            super::PARENT_SECRET
        }
    }
}

fn main() {
    println!("Read from inner: {}", outer::inner::read_parent_secret());
}
Read from inner: 42

Attempting to call a private function inside a child module from the parent (or from main) fails at compile time with E0603:

// ❌ does not compile
mod network {
    fn connect_internal() {}
}

fn main() {
    network::connect_internal();
}
error[E0603]: function `connect_internal` is private
 --> src/main.rs:6:14
  |
6 |     network::connect_internal();
  |              ^^^^^^^^^^^^^^^^ private function
  |
note: the function `connect_internal` is defined here
 --> src/main.rs:2:5
  |
2 |     fn connect_internal() {}
  |     ^^^^^^^^^^^^^^^^^^^^^

error: aborting due to 1 previous error