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
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
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 `pub` keyword to make them accessible outside the module.
TRUE
When accessing a nested module function, only the innermost module and the function need to be declared as `pub` - parent modules can remain private.
FALSE - parent modules must also be public
The `super` keyword is used to navigate up one or more levels in the module hierarchy, while `crate` refers to the root of the current crate for absolute paths.
TRUE - `super` navigates up, `crate` provides global paths
Fields in a struct are public by default, so you need to use the `priv` keyword to make them private within a module.
FALSE - fields are private by default, use `pub` to make them public
Using the `use` statement 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.
TRUE - Don't do that, it can be confusing.
Enter your answers into piazza poll.
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