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) viasuper::orcrate::. - 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 markedpub!
+-------------------------------------------------------------------+
| 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