Better Programming

Advice for programmers.

Follow publication

Explaining Rust’s Modules

Marc Marburger
Better Programming
Published in
7 min readOct 15, 2020

white chess pieces with one rust-colored pawn ready for play
Photo by Randy Fath on Unsplash

It’s Different

When beginning with Rust and having the need to expand to other files in subfolders of the project, one can stumble over the module concept of Rust very fast.

Whatever technology or programming language we use, we tend to structure our ideas and concepts in directories and files, in order to maintain a valuable overview as the project accelerates and expands, to encapsulate knowledge, and to create strong coherence.

Creating these folders in a Rust project, it strikes home fast: Things are very different here. A C-based language programmer tends to import files to get the respective types or namespaces within one’s scope. Playing around with some context help of the IDE or searching the internet, we learn that Rust thinks in modules, not files.

Rust works with modules, as part of a module tree. There is no such thing as file imports.

A Module in Rust

Important concepts in the Rust Module System¹ are packages, crates, modules, and paths. This article focuses on modules² and paths, when defined in multiple files and how to bring the split parts together.

Creating a base project

Creating a new project from a template in one’s favorite IDE (or terminal editor) is usually the first step into a new project, and the Rust project will probably look similar to this:

Our project with src and (build-generated) target folders, along with git and Cargo files. Image by Daniel Flaum

The interesting part, with respect to modules, plays its part in the src subfolder of our project. Looking into the file structure beneath src, we see:

Image by Daniel Flaum

The contents are fairly simple. It’s just the main.rs file, the entry point of our project, containing the main function, usually being created like this:

The src/main.rs (in case of an executable) or src/lib.rs (in case of a library) files are called crate roots.

By contract, main.rs or lib.rs act as the entry point into a package for the Rust compiler rustc.

We can, of course, build and run the project, as should be expected of a raw template. We can set up our base camp right here and talk about how to climb that mountain.

The Plan

We want to build a project that consists of two modules being imported from src/main.rs. These modules will expose public functionality as well as define module-only parts. The project will also demonstrate how modules can use functionality from other modules.

There are two modules: a power plant and a power-consuming tower.

Let’s think of our crate root (src/main.rs) as a street. We want to construct two buildings next to each other: a tower building and an auxiliary power building. These two buildings will be our two modules. Our construction site (project) has dependencies between the street and its buildings, the power plant, generating power, and the tower building consuming it.

Adding files and folders to the project

When defining a module that is to be split into folders and files, we create a directory, and, on level with the crate root (main.rs or lib.rs), another file. Both carry the module name as directory- and filename. A structure for the module example_module would be:

An exemplary module structure

In our case, creating two buildings, or modules, we have to create two directories and two files. This results in a new folder structure in our project:

The project’s src folder structure

The glue

Of course, we have to also write some code to glue together the mere file system items. A module, represented by a directory, can consist of various files. Each of these files might define new types or symbols in Rust. The only missing part is how to bring the parts together. How to connect the buildings with our street?

Rust relies on a module tree in order to resolve all parts necessary to build.

As covered before, the (crate) root of that tree is our src/main.rs file. So somehow, reaching out from this root, we have to tell Rust there is a (tree-)branch to add. Remembering the module-like named file — created along with the directory — we already have something to plug and play.

The referencing for a module example_module would be like so:

Referencing of example_module

Beneath “Seen as modules,” we see a difference between lines 10 and 11 in that one line has declared pub³ and the other not. The keyword pub tells Rust’s module system that the respective module is public to a referencing outer module, such as our crate root (src/main.rs). Module a is only visible within our exemplary_module, whereas module b is visible to the outside.

Note: You tell Rust to make a module, function, or symbol visible with the keyword pub.

The code

The module tree is obviously put together in structuring directories and their respective module-file and in writing some glue code within these files. The code, beginning in src/auxiliary_building/auxiliary.rs, jumping to src/auxiliary_building.rs, and ending in src/main.rs, would be as follows:

The glue for auxiliary_building

What’s that :: thing?

A double colon separates parts of a path⁴. A path can be considered similar to resolving resources by namespaces and type names. We’ll talk about them later on in this article.

The same procedure has to be performed for our second module, or building within the street, the tower building.

The full picture

The crate root, src/main.rs, will access the tower_building module through mod and use keywords. The tower_building module will define its parts within the src/tower_building.rs file, and a file within the src/tower_building (we named it tower.rs above) will define some symbols to be used, completing the circle, in src/main.rs.

This results in the full code as below:

All involved files in this example

What about submodules?

If we want to introduce some folder beneath auxiliary_building, say, a plug module, we act as before: create a folder and a file for it. The new structure would be:

The project structure after adding a plug folder and file

Having created the file system part, we have to add some glue for the module system. As the submodule plug is added beneath the auxiliary_building module, we have to adjust the src/auxiliary_building.rs file accordingly. We add the plug module:

After doing so, we can use whatever is created within the src/auxiliary_building/plug folder in our src/main.rs. I created a function in device.rs and other_device.rs in order to demonstrate the availability:

If the auxiliary.rs file beneath folder src/auxiliary_building were to evolve, you guess right: We can create a new folder, named auxiliary, split code into files, and make available public functionality and symbols in the auxiliary.rs file. An already consuming module might have to adjust the paths accordingly.

Paths, or How to Access a Module From a Module

We used them already: Rust’s paths did do their job already, in that our crate root was able to call functionality, which was made available in our modules.

There are two types of paths: absolute and relative.

Resolving, for example, the generate_energy() function via the path auxiliary_building::auxiliary::generate_energy(), we used an absolute path. They always start from the perspective of the crate root, exactly where our module tree has its origin. This path was resolved from src/main.rs. If we want to use an absolute path from anywhere within our crate, we can use the crate literal.

A relative path, on the other hand, does always take the perspective of the current module, which is the one we are writing our path in. It can be started with the self or super literals.

The literal self will start in the position of the module tree, where the current module is located. In order to navigate up one level in the tree, like in the case of a file system, we use super.

To summarize, there are three literals to start a path: crate (absolute), self, and super (relative).

The following example demonstrates the usage of all three literals:

Demonstrating an absolute and two relative paths, resolving a module from a module

Conclusion

Yes, Rust’s handling of how to add code together is different, but…

Working through this example, we saw how files can relate to modules in Rust and how Rust’s module tree resolves — or understands — other modules, defined in separated files and folder hierarchies.

Additionally, we looked into paths and how we can resolve dependencies, such as other modules, functionalities, or symbols from within a module or any other point in the project’s module tree.

Marc Marburger
Marc Marburger

Written by Marc Marburger

Freelancer and polyglot software developer.

Responses (2)

Write a response

Thank you! _/|\_

--

Thank you. Now, I want to use some templates, simple .txt files. Where would these go, and how to properly load them as strings?

--