13.2Defining modules and paths
Lesson 13.1 introduced modules as named scopes. This lesson makes them practical: how to nest them, how to name something inside one (a path), and how to shorten those paths with use. We'll keep everything in a single file for now; splitting across files is lesson 13.4, and it changes nothing about the ideas here.
One thing up front: by default, everything inside a module is private to that module, so the examples below mark items pub to make them reachable from outside. That keyword is lesson 13.3's whole subject; for now, read pub as "visible outside this module" and don't worry about the details.
Nesting modules
Modules can contain other modules, which lets you build a tree that mirrors how you think about the program. A restaurant's front-of-house operations might nest like this:
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {
println!("added to waitlist");
}
}
pub mod serving {
pub fn take_order() {
println!("order taken");
}
}
}
front_of_house contains hosting and serving, and each of those contains functions. This forms a tree, with the crate itself at the root (the implicit top-level module called crate), front_of_house as a child, and hosting and serving as its children. The structure is exactly the file-system metaphor it resembles: modules are folders, items are files, and a path names a location in the tree.
Paths: naming items in the tree
To call add_to_waitlist from elsewhere, you write a path to it. There are two kinds. An absolute path starts from the crate root with the keyword crate:
fn main() {
crate::front_of_house::hosting::add_to_waitlist();
}
A relative path starts from the current module. From the crate root, front_of_house is right there, so:
fn main() {
front_of_house::hosting::add_to_waitlist();
}
Both reach the same function. Read a path left to right as a route through the tree: "from the crate root, into front_of_house, into hosting, to add_to_waitlist." It's the same :: you've used since String::from; now you can see it was always a path through a module tree, the standard library's tree instead of your own.
self and super
Two more path keywords navigate relative to where you are. self:: means "starting from the current module" (rarely needed but explicit). super:: means "starting from the parent module," the module-tree equivalent of .. in a file path. super is genuinely useful when a child module needs to reach back out to a sibling of its parent:
fn deliver_order() {
println!("order delivered");
}
mod back_of_house {
pub fn fix_incorrect_order() {
cook_order();
super::deliver_order(); // reach up to the parent module
}
fn cook_order() {
println!("cooking");
}
}
fix_incorrect_order lives in back_of_house, and deliver_order lives in the parent (the crate root). super::deliver_order() walks up one level to find it. Without super, the compiler would look for deliver_order inside back_of_house and not find it.
use: bringing names into scope
Writing front_of_house::hosting::add_to_waitlist() every time is tedious. The use keyword brings a path into the current scope so you can refer to it by a shorter name:
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {
println!("added to waitlist");
}
}
}
use crate::front_of_house::hosting;
fn main() {
hosting::add_to_waitlist();
hosting::add_to_waitlist();
}added to waitlist
added to waitlist
After use crate::front_of_house::hosting;, the name hosting is available directly, so the calls shorten to hosting::add_to_waitlist(). Every use std::io; you've written since chapter 1 was doing exactly this: pulling io from the standard library's module tree into scope so you could write io::stdin() instead of std::io::stdin().
Best practice
The convention is to use the parent module of a function (use ...::hosting;, then call hosting::add_to_waitlist()), so the call site still shows which module the function came from. For types, structs, and enums, the convention is the opposite: bring the type itself into scope (use std::collections::HashMap;, then write HashMap). It's a small style rule, and rust-analyzer will add use lines for you, but following it keeps call sites readable.
Renaming with as
When two paths would bring in the same name, or a name is just too long, as gives it a local alias, the same as keyword you'd otherwise meet for casts (lesson 4.10) but a completely separate use:
use std::fmt::Result;
use std::io::Result as IoResult;
Now Result means the formatting one and IoResult means the I/O one, so the two collide no longer. You can also combine related imports on one line with braces, use std::io::{self, Write};, and re-export a name you've brought in with pub use so callers of your module can use it too (a technique lesson 13.5 leans on).
Quiz time
Question #1
Given this tree, write both an absolute and a relative path to call seat_party from main (assume the items are pub):
mod front_of_house {
pub mod hosting {
pub fn seat_party() {}
}
}Show solution
Absolute (from the crate root): crate::front_of_house::hosting::seat_party();. Relative (from main, which is at the crate root): front_of_house::hosting::seat_party();. Both name the same function by walking the same tree from different starting points.
Question #2
What do super:: and crate:: each mean at the start of a path?
Show solution
crate:: starts an absolute path from the crate root (the top of the module tree). super:: starts a relative path from the parent of the current module, like .. in a file path. self:: (the third) starts from the current module.
Question #3
Rewrite this so the call site is shorter, using use, while keeping the module name visible at the call (per the best-practice convention):
fn main() {
crate::utils::math::square(4);
}Show solution
use crate::utils::math;
fn main() {
math::square(4);
}
Bring in the parent module math, then call math::square(4). The call still shows that square comes from math, which is why the convention imports the parent rather than the function itself.
You've now seen pub sprinkled through every example without explanation. The next lesson makes it the main event: visibility, why private-by-default is the right default, and how pub builds the boundaries that let a module hide its internals.