Paths for Referring to an Item in the Module Tree
To show Rust where to find an item in a module tree, we use a path in the same way we use a path when navigating a filesystem. To call a function, we need to know its path.
A path can take two forms:
- An absolute path is the full path starting from a crate root; for code from an external crate, the absolute path begins with the crate name, and for code from the current crate, it starts with the literal
crate
.
- A relative path starts from the current module and uses
self
, super
, or an identifier in the current module.
Both absolute and relative paths are followed by one or more identifiers separated by double colons (::
).
Returning to Listing 7-1, say we want to call the add_to_waitlist
function. This is the same as asking: what's the path of the add_to_waitlist
function? Listing 7-3 contains Listing 7-1 with some of the modules and functions removed.
We'll show two ways to call the add_to_waitlist
function from a new function, eat_at_restaurant
, defined in the crate root. These paths are correct, but there's another problem remaining that will prevent this example from compiling as is. We'll explain why in a bit.
The eat_at_restaurant
function is part of our library crate's public API, so we mark it with the pub
keyword. In "Exposing Paths with the pub Keyword", we'll go into more detail about pub
.
Filename: src/lib.rs
mod front_of_house {
mod hosting {
fn add_to_waitlist() {}
}
}
pub fn eat_at_restaurant() {
// Absolute path
crate::front_of_house::hosting::add_to_waitlist();
// Relative path
front_of_house::hosting::add_to_waitlist();
}
Listing 7-3: Calling the add_to_waitlist
function using absolute and relative paths
The first time we call the add_to_waitlist
function in eat_at_restaurant
, we use an absolute path. The add_to_waitlist
function is defined in the same crate as eat_at_restaurant
, which means we can use the crate
keyword to start an absolute path. We then include each of the successive modules until we make our way to add_to_waitlist
. You can imagine a filesystem with the same structure: we'd specify the path /front_of_house/hosting/add_to_waitlist
to run the add_to_waitlist
program; using the crate
name to start from the crate root is like using /
to start from the filesystem root in your shell.
The second time we call add_to_waitlist
in eat_at_restaurant
, we use a relative path. The path starts with front_of_house
, the name of the module defined at the same level of the module tree as eat_at_restaurant
. Here the filesystem equivalent would be using the path front_of_house/hosting/add_to_waitlist
. Starting with a module name means that the path is relative.
Choosing whether to use a relative or absolute path is a decision you'll make based on your project, and it depends on whether you're more likely to move item definition code separately from or together with the code that uses the item. For example, if we moved the front_of_house
module and the eat_at_restaurant
function into a module named customer_experience
, we'd need to update the absolute path to add_to_waitlist
, but the relative path would still be valid. However, if we moved the eat_at_restaurant
function separately into a module named dining
, the absolute path to the add_to_waitlist
call would stay the same, but the relative path would need to be updated. Our preference in general is to specify absolute paths because it's more likely we'll want to move code definitions and item calls independently of each other.
Let's try to compile Listing 7-3 and find out why it won't compile yet! The errors we get are shown in Listing 7-4.
$ cargo build
Compiling restaurant v0.1.0 (file:///projects/restaurant)
error[E0603]: module `hosting` is private
--> src/lib.rs:9:28
|
9 | crate::front_of_house::hosting::add_to_waitlist();
| ^^^^^^^ private module
|
note: the module `hosting` is defined here
--> src/lib.rs:2:5
|
2 | mod hosting {
| ^^^^^^^^^^^
error[E0603]: module `hosting` is private
--> src/lib.rs:12:21
|
12 | front_of_house::hosting::add_to_waitlist();
| ^^^^^^^ private module
|
note: the module `hosting` is defined here
--> src/lib.rs:2:5
|
2 | mod hosting {
| ^^^^^^^^^^^
Listing 7-4: Compiler errors from building the code in Listing 7-3
The error messages say that module hosting
is private. In other words, we have the correct paths for the hosting
module and the add_to_waitlist
function, but Rust won't let us use them because it doesn't have access to the private sections. In Rust, all items (functions, methods, structs, enums, modules, and constants) are private to parent modules by default. If you want to make an item like a function or struct private, you put it in a module.
Items in a parent module can't use the private items inside child modules, but items in child modules can use the items in their ancestor modules. This is because child modules wrap and hide their implementation details, but the child modules can see the context in which they're defined. To continue with our metaphor, think of the privacy rules as being like the back office of a restaurant: what goes on in there is private to restaurant customers, but office managers can see and do everything in the restaurant they operate.
Rust chose to have the module system function this way so that hiding inner implementation details is the default. That way, you know which parts of the inner code you can change without breaking outer code. However, Rust does give you the option to expose inner parts of child modules' code to outer ancestor modules by using the pub
keyword to make an item public.