0
0
Rustprogramming~15 mins

Module visibility in Rust - Deep Dive

Choose your learning style9 modes available
Overview - Module visibility
What is it?
Module visibility in Rust controls which parts of your code can be seen and used by other parts. It helps you decide if functions, structs, or other items are private to a module or accessible from outside. By default, everything inside a module is private, meaning only code inside that module can use it. You can change this using keywords like pub to make items public.
Why it matters
Without module visibility, all parts of a program would be open to everyone, making it hard to protect important details and causing accidental mistakes. It helps keep code organized and safe by hiding internal details and exposing only what is needed. This makes programs easier to understand, maintain, and less prone to bugs.
Where it fits
Before learning module visibility, you should understand Rust basics like functions, structs, and modules. After this, you can learn about advanced Rust features like crates, privacy in crates, and how visibility affects testing and API design.
Mental Model
Core Idea
Module visibility is like setting doors in a house: you decide which rooms are open to guests and which are private.
Think of it like...
Imagine a house with many rooms. Some rooms have locked doors (private), and some have open doors (public). Only people with keys or permission can enter locked rooms. This controls who sees what inside the house.
House (crate)
├── Living Room (pub module)
│   ├── Sofa (pub function)
│   └── Secret Drawer (private function)
└── Bedroom (private module)
    └── Diary (private struct)
Build-Up - 7 Steps
1
FoundationUnderstanding Rust modules
🤔
Concept: Modules group code into namespaces to organize and separate functionality.
In Rust, you create modules using the mod keyword. Modules can contain functions, structs, and other modules. By default, everything inside a module is private, meaning only code inside that module can access it. Example: mod kitchen { fn cook() { println!("Cooking food"); } } // cook() is private here and cannot be called outside kitchen.
Result
The cook function is hidden from outside the kitchen module.
Understanding that modules create separate spaces helps you organize code and control access.
2
FoundationDefault privacy rules
🤔
Concept: By default, items inside modules are private and not accessible from outside.
If you try to call a private function or use a private struct from outside its module, Rust will give a compile error. Example: mod kitchen { fn cook() {} } fn main() { kitchen::cook(); // Error: function `cook` is private }
Result
Compilation error because cook is private.
Knowing default privacy prevents accidental exposure of internal details.
3
IntermediateMaking items public with pub
🤔Before reading on: do you think adding pub to a function makes it accessible everywhere or only inside the module? Commit to your answer.
Concept: The pub keyword makes functions, structs, or modules accessible from outside their module.
You can add pub before a function, struct, or module to make it public. Example: mod kitchen { pub fn cook() { println!("Cooking food"); } } fn main() { kitchen::cook(); // Works because cook is public }
Result
The cook function can be called from main because it is public.
Understanding pub lets you control what parts of your code are shared and what stays hidden.
4
IntermediatePublic modules and nested visibility
🤔Before reading on: If a module is public but its function inside is private, can you call that function from outside? Commit to your answer.
Concept: Making a module public does not automatically make its contents public; each item needs its own visibility.
You can make a module public with pub mod, but its functions or structs inside remain private unless also marked pub. Example: pub mod kitchen { fn cook() {} // private pub fn clean() {} // public } fn main() { kitchen::clean(); // Works // kitchen::cook(); // Error: private }
Result
Only clean is accessible from outside; cook remains private.
Knowing that visibility is set per item helps you design clear and safe APIs.
5
IntermediateUsing pub(crate) for crate-wide visibility
🤔Before reading on: Does pub(crate) make an item visible only inside the current module or the whole crate? Commit to your answer.
Concept: pub(crate) makes items visible anywhere inside the same crate but not outside it.
You can restrict visibility to the whole crate using pub(crate). Example: mod kitchen { pub(crate) fn cook() { println!("Cooking inside crate"); } } fn main() { kitchen::cook(); // Works inside crate } // Outside this crate, cook() is not accessible.
Result
cook is accessible anywhere in the crate but hidden from other crates.
Understanding pub(crate) helps you share code internally without exposing it publicly.
6
AdvancedVisibility with structs and fields
🤔Before reading on: If a struct is public but its fields are private, can you access those fields directly? Commit to your answer.
Concept: Struct visibility and field visibility are separate; you can make a struct public but keep its fields private.
You can mark a struct as pub but keep its fields private to control how users create or modify it. Example: pub struct Cookie { secret_ingredient: String, // private field pub size: u8, // public field } fn main() { let c = Cookie { secret_ingredient: String::from("Sugar"), size: 5 }; // Error: field is private println!("Size: {}", c.size); // Works }
Result
You cannot set or read private fields from outside, even if the struct is public.
Knowing this separation lets you protect internal data while exposing safe interfaces.
7
ExpertRe-exporting with pub use for API design
🤔Before reading on: Does pub use create a new copy of the item or just a new name for it? Commit to your answer.
Concept: pub use lets you re-export items from modules to create clean public APIs without exposing internal structure.
You can use pub use to bring items from private modules into public scope. Example: mod kitchen { pub fn cook() {} } pub use kitchen::cook; // Re-export cook publicly fn main() { cook(); // Works because of re-export }
Result
cook is accessible directly without exposing the kitchen module.
Understanding re-exporting helps you design simple and stable public interfaces hiding internal complexity.
Under the Hood
Rust's compiler enforces visibility rules at compile time by checking the visibility modifiers on each item. When code tries to access an item, the compiler verifies if the access is allowed based on the module hierarchy and visibility keywords like pub, pub(crate), or private. This prevents unauthorized access before the program runs, ensuring safety and encapsulation.
Why designed this way?
Rust was designed for safety and clarity. Visibility rules prevent accidental misuse of internal code and encourage clear API boundaries. The fine-grained control (private, pub, pub(crate), pub(super)) balances flexibility and safety. Alternatives like all-public or all-private would either expose too much or limit code reuse.
Crate
├── pub mod kitchen
│   ├── pub fn cook()  <--- accessible outside
│   └── fn clean()     <--- private
└── mod bedroom
    └── fn sleep()     <--- private

Access checks:
main -> kitchen::cook()  ✔ allowed
main -> kitchen::clean() ✘ denied
main -> bedroom::sleep() ✘ denied
Myth Busters - 4 Common Misconceptions
Quick: Does making a module public automatically make all its contents public? Commit yes or no.
Common Belief:If a module is public, everything inside it is also public.
Tap to reveal reality
Reality:Making a module public only makes the module itself accessible; its contents remain private unless individually marked pub.
Why it matters:Assuming everything inside is public can cause compile errors and confusion when trying to use items that are still private.
Quick: Can you access private fields of a public struct directly? Commit yes or no.
Common Belief:If a struct is public, all its fields are also public by default.
Tap to reveal reality
Reality:Struct fields are private by default even if the struct is public; each field needs pub to be accessible outside.
Why it matters:This misconception leads to errors when trying to access or set fields, breaking encapsulation.
Quick: Does pub(crate) make an item public to other crates? Commit yes or no.
Common Belief:pub(crate) makes an item public everywhere, including other crates.
Tap to reveal reality
Reality:pub(crate) restricts visibility to the current crate only; other crates cannot access the item.
Why it matters:Misunderstanding this can cause unexpected access errors when sharing code across crates.
Quick: Does pub use create a new copy of the item or just a new name? Commit copy or name.
Common Belief:pub use copies the item into the new module.
Tap to reveal reality
Reality:pub use creates a new name (alias) for the existing item; it does not duplicate code or data.
Why it matters:Thinking it copies can lead to confusion about code size, performance, and behavior.
Expert Zone
1
Visibility can be controlled at multiple levels: pub, pub(crate), pub(super), and private, allowing fine-grained access control.
2
Re-exporting with pub use is a powerful pattern to hide internal module structure while exposing a clean API surface.
3
Private items are still compiled and checked by the compiler, so they can be tested within the crate even if hidden externally.
When NOT to use
Avoid making everything public just to fix access errors; instead, rethink module boundaries or use pub(crate) for internal sharing. For very large projects, consider using separate crates for clear API boundaries instead of relying solely on module visibility.
Production Patterns
In real-world Rust projects, internal modules often remain private with pub(crate) used for sharing within the crate. Public APIs are designed with re-exporting to present a simple interface. Tests often access private items using #[cfg(test)] modules inside the same crate.
Connections
Encapsulation in Object-Oriented Programming
Module visibility in Rust is a form of encapsulation, controlling access to internal details.
Understanding Rust visibility helps grasp how encapsulation protects data and behavior in many programming languages.
Access Control in Operating Systems
Both control who can access resources, whether code items or files/processes.
Learning Rust visibility deepens understanding of access permissions and security in computing systems.
Information Hiding in Software Engineering
Module visibility is a practical way to hide implementation details and expose only necessary interfaces.
Knowing this concept helps design maintainable and robust software by reducing complexity and dependencies.
Common Pitfalls
#1Trying to call a private function from outside its module.
Wrong approach:mod kitchen { fn cook() {} } fn main() { kitchen::cook(); // Error: function is private }
Correct approach:mod kitchen { pub fn cook() {} } fn main() { kitchen::cook(); // Works }
Root cause:Not understanding that functions are private by default and need pub to be accessible.
#2Making a struct public but forgetting to make its fields public.
Wrong approach:pub struct Cookie { secret: String, // private field } fn main() { let c = Cookie { secret: String::from("Sugar") }; // Error: field is private }
Correct approach:pub struct Cookie { pub secret: String, // public field } fn main() { let c = Cookie { secret: String::from("Sugar") }; // Works }
Root cause:Confusing struct visibility with field visibility; fields are private unless pub.
#3Assuming pub(crate) makes items public outside the crate.
Wrong approach:mod kitchen { pub(crate) fn cook() {} } // In another crate fn main() { kitchen::cook(); // Error: function not accessible }
Correct approach:mod kitchen { pub fn cook() {} } // In another crate fn main() { kitchen::cook(); // Works }
Root cause:Misunderstanding the scope of pub(crate) visibility.
Key Takeaways
Rust modules organize code and control access to functions, structs, and other items.
By default, everything inside a module is private and hidden from outside code.
The pub keyword makes items public and accessible outside their module.
Visibility can be fine-tuned with pub(crate), pub(super), and other modifiers for flexible access control.
Re-exporting with pub use helps create clean public APIs while hiding internal module structure.