I’ve been writing small programs in rust as a way to learn the language. While trying to organize one of the application, I realized I didn’t fully understand how the module system worked.
The official docs say that if you have your code organized in a single rs file, you can just split up the module into different files and the same rules apply. Without understanding how the module system actually works, you might be mislead into making false assumptions and hence run into errors that you might not understand how to solve. I think I must have read the documentation 3-4 times before I realize that just skimming over the the documentation wasn’t going to work.
So I went back to basics and broke down the problem. That, and asking a ton of questions on the Rust Discord channel, which was really helpful. I figured I would write this up in case this helps out anyone else.
I assume you already have read the basics of using modules to control scope and privacy. The documentation explains that very well.
Let’s say you have an amazing application, and you have all the code broken down into modules, but everything is in one file.
main.rs
|
|
In general, a very useless little application. But it will demonstrate some of the things I learned along the way.
super
is how a module
can reference an item in its parent module.
So super::super::
is a way of specifying a module that is the grand-parent of the module.
If we were to look at how the modules are organized, they would look like this
|
|
You have 3 modules (foo
, constants
and one
) with a single parent main.
We will call main
our crate root module.
We will discuss this more later on.
Let’s start splitting up the modules. First the constants
module.
I’ll move the code in the constants module into its own file constants.rs
constants.rs
|
|
main.rs
|
|
I added comments to where I made the changes.
Notice however, we have maintained our module hierarchy.
Even though the constants
mod is in its own file, it still has
main.rs
as its root.
Let’s move foo
into its own file next.
foo.rs
|
|
main.rs
|
|
Notice we added mod foo
to main.rs
.
We did the same when moving constants
to its own file.
By adding it to main.rs
, I’m declaring that my crate’s module hierarchy
is still the same as before. I.E., the module foo
is a child module of main
|
|
In the case of my binary crate, my crate root module is main
.
If you have a library crate, the crate root would be lib.rs
.
These are files that must be included in any rust project.
For example, if you were to rename main.rs
to blah.rs
, cargo
will complain when
I try to build the project:
|
|
There are ways to change the defaults, but that is beyond the scope of this discussion.
Why did we add mod foo
to main.rs
?
We did that to maintain the module hierarchy that was set up when we started refactoring.
Why did I add use crate::constants
?
use crate
is an absolute path.
If you want to use a function from a module, we need to know its path in the module hierarchy.
By saying use crate::constants
, we are bring the module constants
, which happens to be a direct child of the crate root, in to the scope of module foo
.
Instead of using use crate::constants
, we could have also used use super::constants
because constants
is the direct child of the crate root, like foo
.
Sibling modules in different files cannot refer to each other directly.
They must reference each other via a common root, which in our case is main
, which also happens
to be the crate root.
We have one more step left.
The module bar
is still in the same file as foo
.
Let’s move bar
to be its own file.
Note: Rust 2018 removed the requirement of always needing a mod.rs
file.
So I wont use mod.rs
for now.
bar
is a submodule of foo
. So let’s set that up by creating a folder called foo
and moving bar
into that folder.
foo/bar.rs
|
|
foo.rs
|
|
No changes were required to main.rs
since it never referenced bar
.
What happened here?
Since bar
is a submodule of foo
, we want to maintain that relationship.
We do that by creating a folder with the same name as foo
and moving bar
there.
We started with a single file main.rs
.
Our application source folder now looks like this:
|
|
Another way we could have split up foo
and bar
was by using mod.rs
.
Instead of having foo.rs
, create mod.rs
under the foo
folder, and move the
contents of foo
into mod.rs
.
foo/mod.rs
|
|
No other changes were required. This is the new folder structure.
|
|
Notice the lack of foo.rs
.
This configuration and the one prior are identical.
This approach is how the module would have been organized prior to Rust 2018.
We are done organizing each module into its own file. Now we can focus on each module on its own, add unit and integration tests in each module without having to worry about breaking other pieces.
Some key pieces to remember
- Every module needs a root. This is why we needed to add
mod foo
andmod constants
tomain.rs
. - There can only be one root. You can’t add
mod foo
to multiple files. - Understand what your module hierarchy looks like and use
self
,super
andcrate
accordinly. - When in doubt, just type out the examples in the docs.
- Really read the docs. Then read them again.