Module Organization
Okay, let’s dive into Rust’s module system, which is crucial for organizing larger projects and managing code complexity. We’ll cover the mod and use keywords, and how to structure your code into logical units.
Module Organization with mod and use in Rust
1. Welcome and Icebreaker:
Welcome back!
Icebreaker: If you were to organize your favorite physical space (like a room, a desk, or a workshop) into “modules,” what would those modules be, and what would each one contain?
2. Outline the Session:
Here’s the plan:
- Concept Explanation: We’ll introduce the concepts of modules, crates, and paths. We’ll cover how to define modules using
mod, how to control visibility withpub, and how to bring items into scope withuse. We’ll also touch on nested modules and file organization. - Live Coding Demonstration: We’ll create a small project with multiple modules and demonstrate how to use
modanduseto organize the code. - Tasks: You’ll get exercises to practice creating and using modules in your own code.
Learning Outcomes: By the end of this session, you will be able to:
- Understand the purpose of modules in Rust.
- Define modules using the
modkeyword. - Control the visibility of items within modules using
pub. - Bring items into scope using the
usekeyword. - Create nested modules.
- Organize modules into separate files.
- Understand the relationship between crates, modules, and files.
Expectations: Module organization is a fundamental aspect of writing maintainable Rust code. Pay close attention to how mod and use work together, and how visibility is controlled.
3. Concept Explanation:
- Crates: A crate is the top-level unit of compilation in Rust. It’s either a binary crate (an executable) or a library crate (code that can be used by other crates). Your
Cargo.tomlfile defines a crate. - Modules: Modules are used to organize code within a crate. They provide a way to group related items (functions, structs, enums, etc.) and control their visibility.
- Paths: Paths are used to refer to items (functions, structs, etc.) within modules. They are like file system paths, but for your code.
mod Keyword:
modis used to define a module.- Module names follow the
snake_caseconvention.
mod my_module {
// Items within the module (functions, structs, etc.)
fn my_function() {}
}
pub Keyword (Visibility):
- By default, items within a module are private to that module. They can only be accessed from within the module itself.
- The
pubkeyword makes an item public, meaning it can be accessed from outside the module.
mod my_module {
pub fn my_public_function() {} // Accessible from outside the module
fn my_private_function() {} // Only accessible from within my_module
}
use Keyword:
usebrings items from other modules into the current scope, so you can refer to them without using their full path.
mod my_module {
pub fn my_function() {}
}
use crate::my_module::my_function; // Bring my_function into the current scope
fn main() {
my_function(); // Now we can call it directly
}
Nested Modules:
Modules can be nested within other modules:
mod outer_module {
pub mod inner_module {
pub fn my_function() {}
}
}
use crate::outer_module::inner_module::my_function;
fn main() {
my_function();
}
File Organization:
src/main.rs: The entry point for a binary crate.src/lib.rs: The entry point for a library crate.- Modules in Separate Files: You can define modules in separate files to keep your code organized.
mod my_module;(insrc/main.rsorsrc/lib.rsor another module file) This tells Rust to look for a file namedmy_module.rs(in the same directory) or a directory namedmy_modulewith a file namedmod.rsinside it.my_module.rs(for simple modules)my_module/mod.rs(for modules that contain submodules)
Example File Structure:
my_project/
├── Cargo.toml
└── src/
├── main.rs
└── my_module.rs
└── my_another_module/
└── mod.rs
4. Live Coding Demonstration:
Let’s create a small library crate to demonstrate these concepts. We’ll start by creating a new project:
cargo new my_library --lib
cd my_library
Now open in your IDE.
src/lib.rs:
// src/lib.rs
pub mod math { // Define a public module named "math"
pub fn add(a: i32, b: i32) -> i32 { // A public function within the "math" module
a + b
}
fn subtract(a: i32, b: i32) -> i32 { // A private function (not accessible outside "math")
a - b
}
pub mod advanced{ //Nested Module
pub fn multiply(a:i32, b:i32) -> i32 {
a * b
}
}
}
pub mod greetings; // Declare a module named "greetings" (defined in greetings.rs)
pub mod farewells { // Define a module named "farewells" inline.
pub fn goodbye() -> String{
"Good bye!".to_string()
}
}
src/greetings.rs:
// src/greetings.rs
pub fn hello() -> String {
"Hello, world!".to_string()
}
src/main.rs:
// src/main.rs
//use my_library::greetings::hello;
//use my_library::math::add;
//use my_library::farewells::goodbye;
use my_library::{greetings::*, math::*, farewells::goodbye}; // Import all public items from the module
fn main() {
println!("{}", hello());
println!("2 + 3 = {}", add(2, 3));
println!("{}", goodbye());
println!("{}", advanced::multiply(2, 3)); // Access nested module
}
Run this code using below command
cargo run
Key Takeaways:
mod: We define modulesmath,greetings, andfarewells.pub: We makemath::add,greetings::hello, andfarewells::goodbyepublic.use: We bring functions into themainfunction’s scope usinguse. We show different ways to useuse(specific imports, wildcard imports).- Separate Files: The
greetingsmodule is defined ingreetings.rs. - Nested Modules: The
advancedmodule is nested within themathmodule. - Inline Modules: The
farewellsmodule is defined directly withinsrc/lib.rs.
5. Tasks
- Create a Geometry Module: Create a new library crate called
geometry. Define a module namedshapeswithin the crate. Inside theshapesmodule, define structs forCircle(with aradiusfield) andRectangle(withwidthandheightfields). Implementareamethods for both structs. Make the structs and theirareamethods public. Write amain.rsthat uses the geometry module. - Nested Modules for a Game: Imagine you’re creating a game. Create a new library crate. Define the following modules:
characters: Contains structs for different character types (e.g.,Player,Enemy).items: Contains structs for different items (e.g.,Weapon,Potion).world: Contains modules for different areas of the game world (e.g.,forest,desert,mountains). Each of these submodules should contain functions related to that area (e.g.,explore,encounter_enemy). Make the necessary items public so that you could potentially use them from amain.rsfile. You don’t need to fully implement the game logic, just focus on the module structure.
- Refactor Existing Code: Take one of your previous projects (e.g., the
BankAccountorTodoListproject) and refactor it to use modules. Move the struct definitions and their implementations into separate modules. - Multiple Files: Create three file.
main.rs,student.rsandteacher.rs. Instudent.rscreateStudentstruct andimplblocks. Inteacher.rscreateTeacherstruct andimplblocks. Use these structs and methods inmain.rs - Visibility: Create a module named
my_modulewith public function, private function, public struct and private struct. And try to access all of these out of the module.