Rust’s module system is surprisingly confusing and causes a lot of frustration for beginners.
In this post, I’ll explain the module system using practical examples so you get a clear understanding of how it works and can immediately start applying this in your projects.
Since Rust’s module system is quite unique, I request the reader to read this post with an open mind and resist comparing it with how modules work in other languages.
Let’s use this file structure to simulate a real world project:
my_project
├── Cargo.toml
└─┬ src
├── main.rs
├── config.rs
├─┬ routes
│ ├── health_route.rs
│ └── user_route.rs
└─┬ models
└── user_model.rs
These are the different ways we should be able to consume our modules:
These 3 examples should be sufficient to explain how Rust’s module system works.
Let’s start with the first example - importing config.rs
in main.rs
.
// main.rs
fn main() {
println!("main");
}
// config.rs
fn print_config() {
println!("config");
}
The first mistake that everyone makes is just because we have files like config.rs
, health_route.rs
etc, we think that these files are modules
and we can import them from other files.
Here’s what we see (file system tree) and what the compiler sees (module tree):
Surprisingly, the compiler only sees the crate
module which is our main.rs
file. This is because we need to explicitly build the module tree in Rust - there’s no implicit mapping between file system tree to module tree.
We need to explicitly build the module tree in Rust, there’s no implicit mapping to file system
To add a file to the module tree, we need to declare that file as a submodule using the mod
keyword. The next thing that confuses people is that you would assume we declare a file as module in the same file. But we need to declare this in a different file! Since we only have main.rs
in the module tree, let’s declare config.rs
as a submodule in main.rs
.
The mod keyword declares a submodule
The mod
keyword has this syntax: