2/5
## Mastering Rust Modules: Organization and Encapsulation Modules are a fundamental feature in Rust for organizing code into logical units. They enable you to group related functionality, control the visibility of items (a concept known as encapsulation), and create distinct namespaces to prevent naming conflicts. This lesson will guide you through defining and using modules, managing visibility, nesting modules, working with structs within modules, and leveraging the `super` keyword for path resolution. ## Defining and Accessing Basic Modules Let's start with a simple Rust program and see how modules help organize it. **Initial State: A Single File Program** Consider a program with a `print()` function called directly from `main()`: ```rust #![allow(unused)] // Attribute to suppress warnings for unused code in examples fn print() { println!("rust"); } fn main() { print(); } ``` **Creating a Module** To better organize our code, we can move the `print()` function into a new module named `my`: ```rust #![allow(unused)] mod my { // Defines the module 'my' fn print() { println!("rust"); } } fn main() { // How do we call print() now that it's in the 'my' module? } ``` **Calling a Function from a Module** To call a function defined within a module from outside that module, you must prefix the function name with the module name and the `::` path separator. ```rust #![allow(unused)] mod my { fn print() { println!("rust"); } } fn main() { my::print(); // This will initially cause an error } ``` Attempting to compile this code will result in an error because, by default, all items (functions, structs, etc.) inside a module are private to that module. **Controlling Visibility with the `pub` Keyword** To make `print()` accessible from outside `mod my`, we must declare it as public using the `pub` keyword. ```rust #![allow(unused)] mod my { pub fn print() { // 'pub' makes this function public println!("rust"); } // This function remains private to 'mod my' fn private_print() { println!("private"); } } fn main() { my::print(); // Now this works // my::private_print(); // This would cause a compile-time error } ``` If you save this code as `examples/mods.rs` (or a similar path structured for Cargo examples) and run it using `cargo run --example mods`, the output will be: ``` rust ``` The `private_print` function, lacking the `pub` keyword, cannot be called from `main()` because it's private to the `my` module. It can, however, be called by other items within the `my` module. ## Structuring Code with Nested Modules Modules can be nested within other modules, allowing for more granular organization. Let's create a new module `a` inside our existing `mod my`. ```rust #![allow(unused)] mod my { pub fn print() { println!("rust"); } fn private_print() { // We can call a function from a nested module 'a' from within 'my' // if 'a::print' is accessible (e.g., public or 'a' is a child) // a::print(); // Example: calling a::print from within the same module scope println!("private"); } // Nested module 'a' // To be accessible from outside 'my' (e.g., from main), 'mod a' itself must be public. pub mod a { // Function inside 'a', also needs to be 'pub' to be called from outside 'mod a'. pub fn print() { println!("a"); } } } fn main() { my::print(); my::a::print(); // To call this, 'mod a' and 'a::print()' must both be public } ``` **Visibility for Nested Modules** For the call `my::a::print()` to work from `main()`: 1. The nested module `a` itself must be declared `pub` within `mod my` (i.e., `pub mod a`). 2. The function `print()` within `mod a` must also be declared `pub` (i.e., `pub fn print()`). If you run the updated code, calling both `my::print()` and `my::a::print()` from `main`, the output will be: ``` rust a ``` ## Encapsulating Data with Structs in Modules Structs, like functions, can be defined within modules. Their visibility, and the visibility of their fields, follows similar rules. Let's define a struct `S` inside our nested module `a`: ```rust #![allow(unused)] mod my { // ... (previous my module content) ... pub mod a { pub fn print() { println!("a"); } // Struct 'S' needs to be 'pub' to be used outside 'mod a' pub struct S { id: u32, name: String, } } } fn main() { // ... } ``` **Visibility of Struct Fields** Even if a struct itself is declared `pub`, its fields are private by default. To access or initialize struct fields from outside the module where the struct is defined, the individual fields must also be marked `pub`. ```rust #![allow(unused)] mod my { // ... pub mod a { pub fn print() { println!("a"); } pub struct S { pub id: u32, // Public field pub name: String, // Public field } } } fn main() { my::print(); my::a::print(); let s = my::a::S { id: 0, name: "rust".to_string(), }; println!("Struct S: id = {}, name = {}", s.id, s.name); } ``` If you tried to initialize `s` while `name` (or `id`) was not `pub`, you would encounter a compile-time error like `field 'name' of struct 'S' is private`. **The Builder Pattern for Structs with Private Fields** A common and robust pattern for initializing structs, especially when you want to control how fields are set or keep some fields private, is to provide a public constructor function (often named `new` or a more descriptive "builder" method) within the struct's module. Consider if we want `S.name` to be private but `S.id` to be public: ```rust #![allow(unused)] mod my { // ... pub mod a { pub fn print() { println!("a"); } pub struct S { pub id: u32, // 'id' is public name: String, // 'name' is private } // Public constructor function for S pub fn build_s(id: u32, initial_name: &str) -> S { S { id, // Shorthand for id: id name: initial_name.to_string(), // Can access private 'name' here } } } } fn main() { my::print(); my::a::print(); // Initialize 's' using the public 'build_s' function let s_instance = my::a::build_s(1, "hello_private_field"); println!("Struct S built: id = {}", s_instance.id); // We cannot directly access s_instance.name here as it's private. // println!("Struct S name: {}", s_instance.name); // This would be an error } ``` This approach works because the `build_s` function is part of `mod a` and therefore has permission to access and initialize the private fields of `struct S`. The builder pattern enhances encapsulation by controlling how struct instances are created and ensuring internal invariants are maintained. ## Navigating Parent Scopes with the `super` Keyword The `super` keyword is a special path qualifier that allows you to refer to the parent module's scope. This is particularly useful for accessing items in sibling modules or items defined in the parent module from within a child module. **Scenario: Accessing Sibling Modules** Imagine we have two sibling modules, `foo` and `my`, at the same level (e.g., directly in `src/main.rs` or `src/lib.rs`). ```rust #![allow(unused)] mod foo { pub fn print() { println!("foo"); } } mod my { // To call foo::print() from within 'my', we need to bring 'foo' into scope. // 'super' refers to the parent scope of 'my' (the crate root in this case), // where 'foo' is also defined. use super::foo; pub fn print_message_from_foo() { foo::print(); // Now callable because 'foo' is in scope via 'use super::foo;' } // ... (other functions and module 'a' from previous examples) ... pub mod a { // ... } } fn main() { my::print_message_from_foo(); } ``` Inside `mod my`, `use super::foo;` tells Rust: "look in the parent module (`super`) for a module named `foo`, and bring it into the current scope." **Using `super` from a Deeper Nested Module** If we want to call `foo::print()` from within `mod a` (which is nested inside `mod my`), we need to use `super` twice: ``` // File/Crate Root // |- mod foo // |- mod my // |- pub mod a // |- // To access 'foo' from here: // // 'super' goes from 'a' to 'my'. // // 'super::super' goes from 'a' to 'my', then from 'my' to the File/Crate Root. ``` Here's the code: ```rust #![allow(unused)] mod foo { pub fn print() { println!("foo"); } } mod my { pub fn print() { println!("rust"); } pub mod a { // To call foo::print() from here: // First 'super' accesses 'my' module's scope. // Second 'super' accesses the crate root scope (parent of 'my'). use super::super::foo; pub fn print() { println!("a"); } pub fn print_message_from_foo_via_a() { foo::print(); // Calls foo::print() from the crate root } } } fn main() { my::print(); my::a::print(); my::a::print_message_from_foo_via_a(); } ``` When this code is run, the output will be: ``` rust a foo ``` The call `my::a::print_message_from_foo_via_a()` successfully executes `foo::print()` by navigating up the module tree using `super::super::foo`. ## Core Principles for Effective Module Usage in Rust To effectively use modules in Rust, keep these key principles in mind: * **Default Privacy:** All items within a module (functions, structs, enums, constants, and other modules) are private by default. They can only be accessed by code within the same module or its direct children. * **The `pub` Keyword:** Use `pub` to make an item public, meaning it can be accessed from outside its defining module. This applies to the module declaration itself if it's nested (`pub mod my_module`), as well as to functions (`pub fn my_func`), structs (`pub struct MyStruct`), and individual struct fields (`pub field_name: Type`). * **Path Separator `::`:** The double colon (`::`) is used to navigate module hierarchies and access items within modules (e.g., `my_module::my_sub_module::my_function()`). * **The `use` Keyword:** This keyword brings paths into the current scope, allowing you to refer to items by shorter names. It's often used with `self`, `super`, or crate names to create more convenient paths. * **The `super` Keyword:** `super` refers to the parent module of the current module. It can be chained (e.g., `super::super::`) to navigate multiple levels up the module hierarchy, enabling access to items in ancestor or sibling modules. * **Builder Pattern:** For structs, especially those with private fields or complex initialization logic, consider providing public constructor functions (often called `new` or following a builder pattern). This enhances encapsulation and provides a controlled interface for creating struct instances. By understanding and applying these concepts, you can write well-organized, maintainable, and robust Rust applications where code is neatly compartmentalized and access is carefully controlled.
Modules are a fundamental feature in Rust for organizing code into logical units. They enable you to group related functionality, control the visibility of items (a concept known as encapsulation), and create distinct namespaces to prevent naming conflicts. This lesson will guide you through defining and using modules, managing visibility, nesting modules, working with structs within modules, and leveraging the super
keyword for path resolution.
Let's start with a simple Rust program and see how modules help organize it.
Initial State: A Single File Program
Consider a program with a print()
function called directly from main()
:
Creating a Module
To better organize our code, we can move the print()
function into a new module named my
:
Calling a Function from a Module
To call a function defined within a module from outside that module, you must prefix the function name with the module name and the ::
path separator.
Attempting to compile this code will result in an error because, by default, all items (functions, structs, etc.) inside a module are private to that module.
Controlling Visibility with the pub
Keyword
To make print()
accessible from outside mod my
, we must declare it as public using the pub
keyword.
If you save this code as examples/mods.rs
(or a similar path structured for Cargo examples) and run it using cargo run --example mods
, the output will be:
The private_print
function, lacking the pub
keyword, cannot be called from main()
because it's private to the my
module. It can, however, be called by other items within the my
module.
Modules can be nested within other modules, allowing for more granular organization. Let's create a new module a
inside our existing mod my
.
Visibility for Nested Modules
For the call my::a::print()
to work from main()
:
The nested module a
itself must be declared pub
within mod my
(i.e., pub mod a
).
The function print()
within mod a
must also be declared pub
(i.e., pub fn print()
).
If you run the updated code, calling both my::print()
and my::a::print()
from main
, the output will be:
Structs, like functions, can be defined within modules. Their visibility, and the visibility of their fields, follows similar rules.
Let's define a struct S
inside our nested module a
:
Visibility of Struct Fields
Even if a struct itself is declared pub
, its fields are private by default. To access or initialize struct fields from outside the module where the struct is defined, the individual fields must also be marked pub
.
If you tried to initialize s
while name
(or id
) was not pub
, you would encounter a compile-time error like field 'name' of struct 'S' is private
.
The Builder Pattern for Structs with Private Fields
A common and robust pattern for initializing structs, especially when you want to control how fields are set or keep some fields private, is to provide a public constructor function (often named new
or a more descriptive "builder" method) within the struct's module.
Consider if we want S.name
to be private but S.id
to be public:
This approach works because the build_s
function is part of mod a
and therefore has permission to access and initialize the private fields of struct S
. The builder pattern enhances encapsulation by controlling how struct instances are created and ensuring internal invariants are maintained.
super
KeywordThe super
keyword is a special path qualifier that allows you to refer to the parent module's scope. This is particularly useful for accessing items in sibling modules or items defined in the parent module from within a child module.
Scenario: Accessing Sibling Modules
Imagine we have two sibling modules, foo
and my
, at the same level (e.g., directly in src/main.rs
or src/lib.rs
).
Inside mod my
, use super::foo;
tells Rust: "look in the parent module (super
) for a module named foo
, and bring it into the current scope."
Using super
from a Deeper Nested Module
If we want to call foo::print()
from within mod a
(which is nested inside mod my
), we need to use super
twice:
Here's the code:
When this code is run, the output will be:
The call my::a::print_message_from_foo_via_a()
successfully executes foo::print()
by navigating up the module tree using super::super::foo
.
To effectively use modules in Rust, keep these key principles in mind:
Default Privacy: All items within a module (functions, structs, enums, constants, and other modules) are private by default. They can only be accessed by code within the same module or its direct children.
The pub
Keyword: Use pub
to make an item public, meaning it can be accessed from outside its defining module. This applies to the module declaration itself if it's nested (pub mod my_module
), as well as to functions (pub fn my_func
), structs (pub struct MyStruct
), and individual struct fields (pub field_name: Type
).
Path Separator ::
: The double colon (::
) is used to navigate module hierarchies and access items within modules (e.g., my_module::my_sub_module::my_function()
).
The use
Keyword: This keyword brings paths into the current scope, allowing you to refer to items by shorter names. It's often used with self
, super
, or crate names to create more convenient paths.
The super
Keyword: super
refers to the parent module of the current module. It can be chained (e.g., super::super::
) to navigate multiple levels up the module hierarchy, enabling access to items in ancestor or sibling modules.
Builder Pattern: For structs, especially those with private fields or complex initialization logic, consider providing public constructor functions (often called new
or following a builder pattern). This enhances encapsulation and provides a controlled interface for creating struct instances.
By understanding and applying these concepts, you can write well-organized, maintainable, and robust Rust applications where code is neatly compartmentalized and access is carefully controlled.
A comprehensive guide to Mastering Rust Modules: Organization and Encapsulation - Learn to structure your Rust code with modules, controlling item visibility using the `pub` keyword. Master nested modules, struct encapsulation with public/private fields, and navigating scopes with `super`.
Previous lesson
Previous
Next lesson
Next
Give us feedback
Course Overview
About the course
Introduction to the Rust programming language
Rust variables and functions
Scalar types, arrays, strings, enum, structs, vectors, and hash maps in Rust
Rust control flows: If / else, if let and let else, loop, match
Rust ownership, including borrow and references
Rust error handling
Rust Modules
Rust Traits
Last updated on August 15, 2025
Rust Developer
Rust Programming BasicsDuration: 6min
Duration: 18min
Duration: 47min
Duration: 15min
Duration: 19min
Duration: 8min
Duration: 12min
Duration: 46min
Duration: 14min
Course Overview
About the course
Introduction to the Rust programming language
Rust variables and functions
Scalar types, arrays, strings, enum, structs, vectors, and hash maps in Rust
Rust control flows: If / else, if let and let else, loop, match
Rust ownership, including borrow and references
Rust error handling
Rust Modules
Rust Traits
Last updated on August 15, 2025