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

  • Try removing the pub keyword 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 pub to 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();  // Note that we still preface with the module name

where_am_i();   // Which instance is called here might surprise you

crate::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! See explanation below.
//use level_1::where_am_i;
//where_am_i();

// But this would work
use level_1::where_am_i as level_1_where_am_i;
level_1_where_am_i();
}

Updated April 2, 2026

Function shadowing works differently than variable shadowing:

  • Lexical Shadowing: When you use use inside a function, that name takes priority over the same name in the global (crate) namespace for the remainder of that scope.
  • The Single-Definition Rule: Unlike variables, you cannot define or import the same name twice in the exact same scope (block). Attempting to use two different paths for the same name in the same block results in a "defined multiple times" compilation error.
  • Conflict Resolution: To use multiple items with the same name in one block, you must either use an alias (e.g., use path::item as nickname;) or call the global version explicitly using the crate:: prefix.

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.
  • 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 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.
  • Fields in a struct are public by default, so you need to use the priv keyword to make them private within a module.
  • 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.

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 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