[Avg. reading time: 13 minutes]
User Defined Modules
Rust allows developers to create their own modules to organize code.
A module groups related functionality together.
Modules can contain:
- Functions
- Structs
- Enums
- Constants
- Traits
- Other modules
This helps large programs remain organized and maintainable.
Default Visibility
Items inside a module are private by default.
To make a function accessible outside the module, use the pub keyword.
Simple Module Example
mod sales { pub fn meet_customer() { println!("meet customer"); } } fn main() { sales::meet_customer(); }
mod sales creates a module
pub fn makes the function visible outside the module
The function is accessed using the module path
sales::meet_customer()
Passing Parameters
Modules behave just like normal Rust functions.
They can receive parameters.
mod sales { pub fn meet_customer(num: i32) { println!("meet customer {num}"); } } fn main() { sales::meet_customer(1); }
Nested Modules
Large systems often require hierarchical structure.
Modules can contain other modules.
Example structure:
departments
└── sales
└── service
└── hr
mod departments { pub mod sales { pub fn meet_customer(num: i32) { println!("meet customer {num}"); } } } fn main() { departments::sales::meet_customer(1); }
crate
├─ main()
└─ departments
└─ sales
- main() and departments are in the same parent scope (crate root).
- So main() can see departments without pub.
- But sales is inside departments, so to expose it to the parent (crate root) it must be pub.
Exposing Limited Functionality
Modules allow internal helper functions to remain private.
Only selected functions are exposed publicly.
mod departments { pub mod sales { pub fn meet_customer(num: i32, requestedby: &str) { println!("meet customer {num}"); let phone_number = get_number(num, requestedby); println!("calling {:?}", phone_number); } fn get_number(num: i32, requestedby: &str) -> String { let phonenumber = match num { 1 => "123-456-7890".to_string(), 2 => "987-654-3210".to_string(), _ => "000-000-0000".to_string(), }; if requestedby == "Manager" { phonenumber } else if requestedby == "CustService" { phonenumber[8..].to_string() } else { "".to_string() } } } } fn main() { departments::sales::meet_customer(1, "Manager"); departments::sales::meet_customer(1, "CustService"); }
- meet_customer() is public
- get_number is private
- main() cannot call get_number()
- This is called encapsulation.
Using super
The super keyword refers to the parent module.
This allows child modules to access functions from their parent module.
mod departments { fn get_number(num: i32) -> String { match num { 1 => "123-456-7890".to_string(), 2 => "987-654-3210".to_string(), _ => "000-000-0000".to_string(), } } pub mod sales { pub fn meet_customer(num: i32) { println!("Sales : meet customer {num}"); let phone_number = super::get_number(num); println!("Sales calling {}", phone_number); } } pub mod service { pub fn meet_customer(num: i32) { println!("Service : meet customer {num}"); let phone_number = super::get_number(num); println!("Service calling {}", phone_number); } } } fn main() { departments::sales::meet_customer(1); departments::service::meet_customer(3); }
Both modules access the same helper function using super.
Using Self
Self usage is optional, added for better clarity. It is useful when calling functions within the same module.
mod departments { fn get_number(num: i32) -> String { match num { 1 => "123-456-7890".to_string(), 2 => "987-654-3210".to_string(), _ => "000-000-0000".to_string(), } } pub mod service { pub fn meet_customer(num: i32) { let phone_number = super::get_number(num); let ticket_number = self::get_service_ticket_number(num); println!( "Calling {phone_number} with ticket number {ticket_number}" ); } fn get_service_ticket_number(num: i32) -> i32 { match num { 1 => 2452423, 2 => 2341332, _ => 6868765, } } } } fn main() { departments::service::meet_customer(3); }
self::get_service_ticket_number()
means “call a function in the same module”.
Writing Tests Inside Modules
Rust allows unit tests to be written inside modules.
Test modules are compiled only when running tests.
cargo new demo_mod_test
mod departments {
fn get_number(num: i32) -> String {
match num {
1 => "123-456-7890".to_string(),
2 => "987-654-3210".to_string(),
_ => "000-000-0000".to_string(),
}
}
#[cfg(test)]
mod tests {
#[test]
fn test_customer_phone() {
assert_eq!(
"000-000-0000",
super::get_number(4)
);
}
}
}
cargo test
- #[cfg(test)] compiles the module only during testing
- #[test] marks a test function
- assert_eq! validates expected output
Summary
- Modules organize Rust code into logical units
- Items are private by default
- pub exposes functions outside the module
- Modules can be nested
- super accesses the parent module
- self refers to the current module
- Tests can be embedded inside modules