When I started learning Rust, the module system did not at first seem to be a shining beacon of intuitive design. The Rust documentation is phenomenal, but there are definitely some areas that I found difficult to follow; this being one such topic. So I thought I might take a stab at writing up a guide that I think would have helped me through the awkward growing pains a bit quicker.
This article is not intended to explain all of the intricacies of the Rust module system, nor is it for demonstrating proper project structure. It’s simply a quick overview of the actual mechanics around using modules in Rust.
Start your stop watch….now!
The View at 30,000 Feet
There are three general ways to declare a module in rust.
- Embedded in an existing source file.
- As a separate file next to your
- As a separate folder leveraging a
With flexibility often comes complexity and confusion, so let’s unpack each of those.
These are modules that you would create to break up certain parts of your file into different namespaces. It looks something like this:
In case it wasn’t obvious before, modules are used for isolating and packaging up some code for use elsewhere. As the comments explain, only public members of the module are available to external callers.
In my somewhat limited experience writing Rust, this seems to be one of the least useful forms modules take. That said, it’s still important to know and could certainly be the right tool depending on what you’re building.
These are typically modules that you want to separate from the rest of your code, but don’t contain any sub-modules of their own (although there is no such constraint*). They look something like this:
If you’re paying attention, you’ll notice that the code hasn’t really changed at all. We’ve moved what was in the old
mod block into its own file named
doorman.rs . The naming is important here! The
mod declaration in
main.rs lets the rust compiler know that it needs to look for a file specifically named
doorman.rs and make that code available under the
These are typically modules that have sub-modules within them. If you imagine having multiple, smaller libraries that make up a larger project, this is likely the approach to take. Here’s what the simplest example looks like:
Notice a difference? It’s subtle, right? The only change is we’ve moved
src/doorman/mod.rs . So when you have a module declaration like
mod doorman; , the Rust compiler is looking for either a
doorman.rs or a
doorman/mod.rs file in the same directory as the declaration.
Admittedly, this particular example might leave you asking “What’s the point? Why not just always use
Back when we were looking at the module files example, our program was made up of a main “module”,
main.rs, and a doorman module,
doorman.rs. The same exact principles can be applied to a module itself. This becomes useful when organizing a big, complex project into smaller, bite-size pieces. In keeping with the doorman example, we could refactor into something like this:
Now lets remember what we’ve learned so far and see if we can figure out what the Rust compiler is going to do with this.
mod doorman;declaration in
main.rstells the Rust compiler that there’s a module named
doormansomewhere in the
- The compiler will find the
doormandirectory and the
mod.rsfile inside of it, loading that file as the
mod.rswe find two more mod declarations,
pub mod greeter;and
pub mod closer;. The compiler again looks for modules, this time named
closer, but in the
doormandirectory. It’s also important to note that the declarations use the
pubkeyword. This exposes both the
closermodules as accessible outside of the
- The files
src/doorman/closer.rsare found and loaded as their respective modules. Making both the
- Finally, from our
main()function, we can greet the user with
doorman::greeter::greet(name);and say goodbye to them with
That’s it! Now you should be able to break that giant
main.rs into dozens of little modules. Or not! That’s the power of the module system, once you’ve wrapped your head around it. As a programmer, you’re given the freedom to choose the project structure and levels of indirection that make sense for your program. But at the same time, just about every Rust project you come across will be structured predictably (albeit in some combination of the three methods we discussed today). There also isn’t any additional syntax you have to include in your files that tell Rust what module or package they should be in. It’s all driven by convention.
It’s also important to realize that none of this even touches on crates, which is another packaging/isolation construct that works at an even higher level than modules. Crates, however, are much more similar in principle to the code packaging you’re probably already familiar with from other languages. So I think you’ll find them much easier to grok if you haven’t already.
*There really aren’t any constraints to when you should use one form versus another. It’s entirely up to you as the programmer.
For more detailed information: