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:

Pre-lecture Reflections

Before class, consider these questions:

  1. Why is code organization important in larger software projects?
  2. What are the benefits of controlling which parts of your code are public vs. private?
  3. How do namespaces prevent naming conflicts in large codebases?
  4. When would you organize code into separate files vs. keeping it in one file?
  5. 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 pub and private visibility
  • Navigate module hierarchies using paths and use statements
  • 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 pub to 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 pub to 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 pub to 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 pub to make fields public
  • Use use to import things into the current scope
  • Use mod to create modules
  • Use crate and super to navigate the module hierarchy