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 fooandmod constantstomain.rs. - There can only be one root. You can’t add
mod footo multiple files. - Understand what your module hierarchy looks like and use
self,superandcrateaccordinly. - When in doubt, just type out the examples in the docs.
- Really read the docs. Then read them again.