Rust Modules In Less Than 5 Minutes

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.

*DISCLAIMER*
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.

  1. Embedded in an existing source file.
  2. As a separate file next to your lib.rs or main.rs
  3. As a separate folder leveraging a mod.rs file.

With flexibility often comes complexity and confusion, so let’s unpack each of those.

Embedded Modules

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.

Module Files

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 doorman:: namespace.

Module Folders

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.rs to 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 doorman.rs?”.

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.

  1. The mod doorman; declaration in main.rs tells the Rust compiler that there’s a module named doorman somewhere in the src directory.
  2. The compiler will find the doorman directory and the mod.rs file inside of it, loading that file as the doorman module.
  3. Inside mod.rs we find two more mod declarations, pub mod greeter; and pub mod closer; . The compiler again looks for modules, this time named greeter and closer, but in the doorman directory. It’s also important to note that the declarations use the pub keyword. This exposes both the greeter and the closer modules as accessible outside of the doorman module.
  4. The files src/doorman/greeter.rs and src/doorman/closer.rs are found and loaded as their respective modules. Making both the greet() and close() functions accessible.
  5. Finally, from our main() function, we can greet the user with doorman::greeter::greet(name); and say goodbye to them with doorman::closer::close(name);

Final Thoughts

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:
https://doc.rust-lang.org/1.1.0/book/crates-and-modules.html