Rust Module System
The rust compiler makes little/no assumptions about the structure of your project. You’re required to construct your entire module tree yourself. To make our work easy, it provides us with some building blocks for this construction
The rust module systems works similar to modern filesystems. There’s a parent & some children. That said, rust has a unique way of doing everything you already know, so I’ll advice you approach this note on a blank state and try to draw parallels later.
The entry-point of modules in rust terminology is called a crate root.
A crate root can either be 2 things, src/main.rs
or src/lib.rs
.
A src/main.rs
is called a binary crate root.
A src/lib.rs
is called a library crate root.
src
└lib.rs
src
└main.rs
When you pass a rust project with this structure to the compiler, the only thing it sees is “crate”.
That is, src/main.rs
-> crate & src/lib.rs
-> crate.
For this note, I’m going to build my module tree on the src/lib.rs
base.
To create a library crate, run cargo new --lib africa
in your terminal.
Now to define a module, we use the mod
keyword. A few notes on this keyword.
- mod creates a scope/boundary for your module
- mod can search for a file, read the content of the file and create a scope around it.
- contents of mod are private by default, so we use the "pub" keyword to make them public.
mod Country {}
- This creates a module called country. The scope/boundary of Country is demarcated by the “{“ and “}”.
- Everything defined within the curly braces falls under the boundary of Country. Just like how borders work.
Let's represent africa using rust's module sytem.
Inside src/lib.rs
, let's place this code.
mod countries {
mod ghana {
fn accra() {
println!("capital of ghana")
}
}
mod nigeria {
fn lagos() {
println!("capital of nigeria")
}
}
}
Even though our project looks like this
└─ src
└─ lib.rs
This is what the rust compiler actually sees
└─ crate
└─ countries
Where's everything else? Hidden.
Hidden in the countries
scope. To make them public, use the pub keyword.
Note that, items within a scope can see what's around them (without needing the pub keyword)
mod countries {
pub mod ghana {
pub fn accra() {
println!("capital of ghana")
}
}
pub mod nigeria {
pub fn lagos() {
println!("capital of nigeria")
}
}
}
Now that we've made the children public, this is how the compiler sees our module tree.
└─ crate
└─ countries
├─ ghana
│ └─ accra
└─ nigeria
└─ lagos
If this were your file system using the absolute path, you'll access it this way.
~/crate/countries/ghana
or ~/crate/countries/nigeria
It's no different in rust, this is how you'll acess these modules using absolute path.
crate::countries::ghana
or crate::countries::nigeria
Absolute paths are great, but verbose. what if we wanted to use relative paths. If someone in accra wants to communicate with someone in lagos, they can use either 1 of these addresses.
From accra, absolute address to lagos
- filesystem path -
~/crate/countries/nigeria/lagos
- rust module path -
crate::countries::nigeria::lagos
From accra, relative address to lagos
- filesystem path -
../nigeria/lagos
- rust module path -
super::nigeria::lagos
The super keyword is used to refers to the parent of a module. In ghana(accra), super refers to countries. In nigeria(lagos), super refers to countries.
There's also the self
keyword, which refers to the module itself.
In ghana(accra), self refers to ghana.
In nigeria(lagos), self refers to nigeria.