Modules and Organization
About This Module
This module introduces Rust's module system for organizing code into logical namespaces. You'll learn how to create modules, control visibility with public/private access, navigate module hierarchies, and organize code across multiple files.
Prework
Prework Readings
Read the following sections from "The Rust Programming Language" book:
- Chapter 7: Managing Growing Projects with Packages, Crates, and Modules - Complete chapter
- Chapter 7.2: Defining Modules to Control Scope and Privacy
- Chapter 7.4: Bringing Paths into Scope with the use Keyword
Pre-lecture Reflections
Before class, consider these questions:
- Why is code organization important in larger software projects?
- What are the benefits of controlling which parts of your code are public vs. private?
- How do namespaces prevent naming conflicts in large codebases?
- When would you organize code into separate files vs. keeping it in one file?
- How do module systems help with code maintainability and collaboration?
Learning Objectives
By the end of this module, you should be able to:
- Create and organize code using Rust's module system
- Control access to code using
puband private visibility - Navigate module hierarchies using paths and
usestatements - Organize modules across multiple files and directories
- Design clean module interfaces for code reusability
- Apply module patterns to structure larger programs
Introduction to Modules
Up to now: our functions and data types (mostly) in the same namespace:
- exception: functions in structs and enums
Question: What is a namespace?
One can create a namespace, using mod
mod things_to_say { fn say_hi() { say("Hi"); } fn say_bye() { say("Bye"); } fn say(what: &str) { println!("{}!",what); } } fn main() {}
Intro, continued...
You have to use the module name to refer to a function.
That's necessary, but not sufficient!
mod things_to_say { fn say_hi() { say("Hi"); } fn say_bye() { say("Bye"); } fn say(what: &str) { println!("{}!",what); } } fn main() { // ERROR: function `say_hi` is private things_to_say::say_hi(); }
Module Basics
-
By default, all definitions in the namespace are private.
-
Advantage: Can hide all internally used code and control external interface
-
Use
pubto make functions or types public -
Try removing the
pubkeyword from the functions and see what happens.
mod things_to_say { pub fn say_hi() { say("Hi"); } pub fn say_bye() { say("Bye"); } fn say(what: &str) { println!("{}!",what); } } fn main() { things_to_say::say_hi(); things_to_say::say_bye(); // ERROR: function `say` is private //things_to_say::say("Say what??"); }
Why modules?
-
limit number of additional identifiers in the main namespace
-
organize your codebase into meaningful parts
-
hide auxiliary internal code
-
By default, all definitions in the namespace are private.
-
Advantage: one can hide all internally used code and publish an external interface
-
Ideally you semantically version your external interface. See https://semver.org
-
Use
pubto make functions or types public
Nesting possible
- but be careful
mod level_1 { mod level_2_1 { mod level_3 { pub fn where_am_i() {println!("3");} } pub fn where_am_i() {println!("2_1");} } mod level_2_2 { pub fn where_am_i() {println!("2_2");} } pub fn where_am_i() {println!("1");} } fn main() { level_1::level_2_1::level_3::where_am_i(); }
Nesting, continued...
But all parent modules have to be public as well.
mod level_1 { pub mod level_2_1 { pub mod level_3 { pub fn where_am_i() {println!("3");} } pub fn where_am_i() {println!("2_1");} } pub mod level_2_2 { pub fn where_am_i() {println!("2_2");} } pub fn where_am_i() {println!("1");} } fn main() { level_1::level_2_2::where_am_i(); }
Module Hierarchy
level_1
├── level_2_1
│ └── level_3
│ └── where_am_i
│ └── where_am_i
├── level_2_2
│ └── where_am_i
└── where_am_i
Paths to modules
pub mod level_1 { pub mod level_2_1 { pub mod level_3 { pub fn where_am_i() {println!("3");} pub fn call_someone_else() { where_am_i(); } } pub fn where_am_i() {println!("2_1");} } pub mod level_2_2 { pub fn where_am_i() {println!("2_2");} } pub fn where_am_i() {println!("1");} } fn where_am_i() {println!("main namespace");} fn main() { level_1::level_2_1::level_3::call_someone_else(); }
Question: What will be printed?
Paths to modules
Global paths: start from crate
mod level_1 { pub mod level_2_1 { pub mod level_3 { pub fn where_am_i() {println!("3");} pub fn call_someone_else() { crate::where_am_i(); crate::level_1::level_2_2:: where_am_i(); where_am_i(); } } pub fn where_am_i() {println!("2_1");} } pub mod level_2_2 { pub fn where_am_i() {println!("2_2");} } pub fn where_am_i() {println!("1");} } fn where_am_i() {println!("main namespace");} fn main() { level_1::level_2_1::level_3::call_someone_else(); }
Question: What will be printed?
Paths to modules
Local paths:
- going one or many levels up via
super
mod level_1 { pub mod level_2_1 { pub mod level_3 { pub fn where_am_i() {println!("3");} pub fn call_someone_else() { super::where_am_i(); super::super::where_am_i(); super::super:: level_2_2::where_am_i(); } } pub fn where_am_i() {println!("2_1");} } pub mod level_2_2 { pub fn where_am_i() {println!("2_2");} } pub fn where_am_i() {println!("1");} } fn where_am_i() {println!("main namespace");} fn main() { level_1::level_2_1::level_3::call_someone_else(); }
Question: What will be printed?
use to import things into the current scope
mod level_1 { pub mod level_2_1 { pub mod level_3 { pub fn where_am_i() {println!("3");} pub fn call_someone_else() { super::where_am_i(); } pub fn i_am_here() {println!("I am here");} } pub fn where_am_i() {println!("2_1");} } pub mod level_2_2 { pub fn where_am_i() {println!("2_2");} } pub fn where_am_i() {println!("1");} } fn where_am_i() {println!("main namespace");} fn main() { // Bring a submodule to current scope: use level_1::level_2_2; level_2_2::where_am_i(); // Bring a specific function/type to current scope: // (Don't do that, it can be confusing). use level_1::level_2_1::where_am_i; where_am_i(); // Bring multiple items to current scope: use level_1::level_2_1::level_3::{call_someone_else, i_am_here}; call_someone_else(); i_am_here(); // ERROR: Name clash! Won't work! //use level_1::where_am_i; //where_am_i(); }
Structs within modules
- You can put structs and methods in modules
- Fields are private by default
- Use
pubto make fields public
pub mod test { #[derive(Debug)] pub struct Point { x: i32, pub y: i32, } impl Point { pub fn create(x:i32,y:i32) -> Point { Point{x,y} } } } use test::Point; fn main() { let mut p = Point::create(2,3); println!("{:?}",p); p.x = 3; // Error: try commenting this out p.y = 4; // Why does this work? println!("{:?}",p); }
Structs within modules
Make fields and functions public to be accessible
mod test { #[derive(Debug)] pub struct Point { pub x: i32, y: i32, // still private } impl Point { pub fn create(x:i32,y:i32) -> Point { Point{x,y} } // public function can access private data pub fn update_y(&mut self, y:i32) { self.y = y; } } } use test::Point; fn main() { let mut p = Point::create(2,3); println!("{:?}",p); p.x = 3; println!("{:?}",p); p.update_y(2022); // only way to update y println!("{:?}",p); // The create function seemed trivial in the past but the following won't work: //let mut q = Point{x: 4, y: 5}; }
True/False Statements on Rust Modules
- In Rust, all definitions within a module are private by default, and you must use the
pubkeyword to make them accessible outside the module. - When accessing a nested module function, only the innermost module and the function need to be declared as
pub- parent modules can remain private. - The
superkeyword is used to navigate up one or more levels in the module hierarchy, whilecraterefers to the root of the current crate for absolute paths. - Fields in a struct are public by default, so you need to use the
privkeyword to make them private within a module. - Using the
usestatement to bring a submodule into scope is recommended, but bringing individual functions directly into the current scope can be confusing and is discouraged in the lecture.
Enter your answers into gradescope.
Solutions will be posted after the lecture.
Recap
- You can put structs and methods in modules
- Fields are private by default
- Use
pubto make fields public - Use
useto import things into the current scope - Use
modto create modules - Use
crateandsuperto navigate the module hierarchy