The Module System, Visibility & Crate Boundaries

Master Rust's module hierarchy, private-by-default encapsulation, fine-grained visibility qualifiers, and compilation boundaries.

01. Foundations & Type System Mechanics6 min read

Rust structures software into a rigorous hierarchy of Crates, Modules, and Items. Rather than relying on file-system paths alone, Rust treats module hierarchies as a logical tree where visibility is strictly private by default.

Understanding these boundaries is essential for designing resilient software architectures and optimizing compiler caching.

Default VisibilityEncapsulation
Private

All items in Rust are completely private to their enclosing module unless explicitly qualified.

1. The Crate as the Atomic Compilation Unit#

In Rust, the crate is the fundamental atomic unit of compilation:

  • rustc compiles one crate at a time into a native binary (.exe, ELF binary) or static/dynamic library (.rlib, .so, .dylib).
  • Incremental compilation and compiler dependency caching operate at the crate boundary.
  • Splitting a large monolithic project into a Cargo Workspace with multiple crates allows parallelized compilation across CPU cores.
Architecture Diagram
Rendering diagram...

2. Formal Visibility Rules#

Let MM be a module node in the module tree G=(V,E)G=(V, E). The visibility rule Vis(I)Vis(I) determining whether an accessing module AA is permitted to reference item I∈MI \in M is defined formally:

\text{Allowed} & \text{if } A = M \quad \text{or } A \text{ is a child descendant of } M \\ \text{Allowed} & \text{if } I \text{ is marked pub and } Vis(M) = \text{Allowed} \\ \text{Denied} & \text{otherwise}

In Rust, parent modules cannot see private items of child submodules, but child submodules can always see private items of their parent ancestors.

Architecture Diagram
Rendering diagram...

3. Fine-Grained Visibility Modifiers#

Rust provides precise visibility qualifiers beyond standard public and private:

Visibility ModifierScope of AccessArchitectural Use Case
(None / Private)Accessible only within the declaring module and its child submodules.Internal helper routines.
pubAccessible to any external crate importing this module.Public library API surface.
pub(crate)Accessible everywhere within the current crate, hidden from consumers.Internal subsystem communication.
pub(super)Accessible only to the immediate parent module.Narrow submodule sharing.
pub(in path)Accessible only within a specific designated ancestor module path.Constrained subsystem visibility.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
pub mod engine { // Hidden from external crates, visible throughout this crate pub(crate) struct InternalState { pub(crate) tick_counter: u64, } pub mod telemetry { // Visible only to immediate parent 'engine' pub(super) fn reset_counters() { println!("Resetting internal counters"); } // Public to everyone if engine is also public pub fn get_metrics() -> u32 { 42 } } }

4. Re-exporting Clean APIs with pub use#

Internal implementation directories often require deep, nested nesting for clarity. However, exposing deep module paths forces library consumers into unwieldy import statements:

1
2
3
4
5
6
7
8
9
10
11
12
// Internal module structure mod storage { pub mod backend { pub struct DatabaseConnection; } } // Re-export to present a clean, flat public facade pub use storage::backend::DatabaseConnection; // Library consumers can now simply write: // use my_crate::DatabaseConnection;
API Stability
Using pub use facades allows you to refactor your internal directory structure and module layout without introducing breaking changes to external consumers.

5. Concept Verification#

Test your understanding of Rust modules, visibility modifiers, and crate compilation boundaries:

Concept check

Which module can access a function marked with pub(super)?

Concept check

What is the primary compilation boundary that rustc uses for incremental code generation and linking?