Rust Modules and Visibility
Rust’s module system organizes code into logical units, controls visibility, and manages access using mod
, pub
, and module paths. Below, I’ll explain the key concepts and provide code examples for different cases, keeping it concise yet comprehensive.
Key Concepts
-
Modules (
mod
)- Defined with the
mod
keyword to group related code (functions, structs, enums, etc.). - Modules can be nested and are declared either inline or in separate files.
- By default, module contents are private.
- Defined with the
-
Visibility (
pub
)- The
pub
keyword makes items (functions, structs, etc.) accessible outside their module. - Without
pub
, items are private to their module or parent module. - Fine-grained control is possible, e.g.,
pub(crate)
limits visibility to the current crate.
- The
-
Module Paths
- Access items using paths: absolute (
crate::module::item
) or relative (self::item
,super::item
). - The
use
keyword imports items to simplify paths.
- Access items using paths: absolute (
-
File-based Modules
- Modules can be defined in separate files, automatically recognized by Rust based on file names.
- Use
mod.rs
for directory-based modules or direct file names for single-file modules.
Code Examples
1. Basic Module and Visibility
Demonstrates mod
, pub
, and private items.
mod my_module {
// Private by default
fn private_function() {
println!("This is private");
}
// Public function
pub fn public_function() {
println!("This is public");
private_function(); // Can call private function within the same module
}
}
fn main() {
// my_module::private_function(); // Error: private_function is not accessible
my_module::public_function(); // Works: prints "This is public" and "This is private"
}
Explanation:
private_function
is inaccessible outsidemy_module
.public_function
is accessible and can callprivate_function
within the same module.
2. Nested Modules and pub
Variants
Shows nested modules and visibility control with pub
, pub(crate)
, and pub(super)
.
mod outer {
pub fn outer_function() {
println!("Outer function");
}
pub(crate) mod inner {
pub fn inner_function() {
println!("Inner function");
}
pub(super) fn super_function() {
println!("Accessible in parent (outer)");
}
}
fn call_inner() {
inner::inner_function(); // Accessible within the same crate
inner::super_function(); // Accessible in parent module
}
}
fn main() {
outer::outer_function(); // Works
outer::inner::inner_function(); // Works: inner_function is pub and accessible in crate
// outer::inner::super_function(); // Error: super_function is only accessible in parent
// outer::call_inner(); // Error: call_inner is private
}
Explanation:
pub(crate)
makesinner_function
accessible within the crate.pub(super)
restrictssuper_function
to the parent module (outer
).call_inner
is private, so it’s inaccessible inmain
.
3. Module Paths and use
Illustrates absolute and relative paths with use
for imports.
mod frontend {
pub mod ui {
pub fn render() {
println!("Rendering UI");
}
}
}
mod backend {
pub fn process() {
println!("Processing data");
}
}
use crate::frontend::ui::render; // Absolute path
use self::backend::process; // Relative path
fn main() {
render(); // Imported, so no need for full path
process();
}
Explanation:
use crate::frontend::ui::render
importsrender
using an absolute path.use self::backend::process
uses a relative path from the current module.- Imports simplify calling functions without full paths.
4. File-based Modules
Shows how to organize modules across files.
Directory Structure:
src/
main.rs
utils.rs
services/
mod.rs
auth.rs
src/main.rs:
mod utils; // Loads utils.rs
mod services; // Loads services/mod.rs
fn main() {
utils::greet(); // Call function from utils.rs
services::auth::login(); // Call function from services/auth.rs
}
src/utils.rs:
pub fn greet() {
println!("Hello from utils!");
}
src/services/mod.rs:
pub mod auth; // Declares auth module (loads auth.rs)
src/services/auth.rs:
pub fn login() {
println!("Logging in...");
}
Explanation:
mod utils;
loadsutils.rs
as a module.mod services;
loadsservices/mod.rs
, which in turn declares theauth
module.- Functions in
utils.rs
andauth.rs
are accessible if markedpub
.
5. Re-exporting with pub use
Demonstrates re-exporting items for easier access.
mod deep {
pub mod nested {
pub fn hidden_function() {
println!("Deeply nested function");
}
}
}
// Re-export nested function to crate root
pub use deep::nested::hidden_function;
fn main() {
hidden_function(); // Directly accessible due to re-export
}
Explanation:
pub use
makeshidden_function
available at the crate root, simplifying access.
6. Structs and Enums in Modules
Shows visibility with structs and enums.
mod shapes {
pub struct Circle {
pub radius: f32, // Public field
center: (f32, f32), // Private field
}
pub enum ShapeType {
Circle,
Square,
}
impl Circle {
pub fn new(radius: f32, center: (f32, f32)) -> Self {
Circle { radius, center }
}
}
}
fn main() {
let circle = shapes::Circle::new(5.0, (0.0, 0.0));
println!("Radius: {}", circle.radius); // Works
// println!("Center: {:?}", circle.center); // Error: center is private
}
Explanation:
Circle
and itsradius
field are public, butcenter
is private.ShapeType
enum variants are public if the enum is public.
Key Takeaways
- Use
mod
to define modules,pub
to control visibility. - Paths (
crate::
,self::
,super::
) navigate module hierarchies. use
simplifies access, andpub use
re-exports items.- File-based modules map to file/directory structures.
- Visibility applies to structs, enums, and their fields/methods.
For more details, check the Rust Book: https://doc.rust-lang.org/book/ch07-00-managing-growing-projects-with-packages-crates-and-modules.html.