The module system in Rust is the way to divide code into hierarchical and reusable pieces.
When you start with Rust, everything is happiness in your src/main.rs file. As you add structs, enums, and functions, the file becomes unmanageable.
In other languages (like C# or Java), the file name and its location are sometimes suggestions or conventions. In Node.js, you just do require('./file') to load whatever you want.
In Rust, the module system is explicit and hierarchical. If you don’t declare a module, the compiler won’t even know the file exists, even if it’s in the correct folder.
Today we’ll see how to go from the chaos of a single file to the order of modules.
What is a module?
A module is a container for code. Think of it as a “namespace” that groups related functions, structs, and other types.
Before touching files, let’s see what a module looks like within a single file:
// src/main.rs
// Define a module called 'network'
mod network {
fn connect() {
println!("Connecting...");
}
}
fn main() {
// To use it, we reference the module
// Note: This will fail because 'connect' is private,
// but it illustrates the hierarchy.
network::connect();
}The keyword is mod. Everything inside the braces {} belongs to the network module.
Extracting modules to other files
Writing modules inside main.rs doesn’t solve the problem of having a giant file. We want to move network to its own file.
Rust is strict about this. To extract the network module, we do this:
In src/main.rs:
We change the code block to a semicolon.
// src/main.rs
mod network; // <--- Rust: "Look for the content of this module in another file"
fn main() {
network::connect();
}In the file src/network.rs:
We copy the content (without the mod keyword or braces).
// src/network.rs
pub fn connect() { // We add 'pub' so we can see it from main
println!("Connecting...");
}Common beginner mistake:
Many create the network.rs file but forget to put mod network; in main.rs.
If you do that, the network.rs file is completely ignored. Rust does not compile all files in the folder, only those connected to the module tree starting from main.rs (or lib.rs).
Submodules and folders
What if our network module grows and we want to split it into network::server and network::client?
We need to nest modules. This point has historically caused much confusion because Rust changed the way to do it in 2018, but you’ll still see old tutorials.
The preferred way since Rust 2018
Rust allows the file that defines a module to have the same name as the folder containing the submodules.
Structure:
src/
├── main.rs (declares 'mod network;')
├── network.rs (declares 'mod server;' and 'mod client;')
└── network/
├── server.rs
└── client.rsLet’s see the code for this modern structure:
src/main.rs
The main entry point.
mod network; // Loads network.rs
fn main() {
network::server::start();
}src/network.rs
This file acts as the “index” for the network/ folder.
// We look for submodules in the 'network/' folder
pub mod server;
pub mod client;
pub fn common_function() {
println!("Something common to the network");
}src/network/server.rs
The actual server code.
pub fn start() {
println!("Server started");
}The module tree
It’s important to visualize your project as a tree, not a flat file system.
- The Root: It’s
src/main.rs(for binaries) orsrc/lib.rs(for libraries). - The Branches: They are the
mod xxx;declarations. - The Leaves: They are functions, structs, and enums.
To access any element, we follow the path from the root or relatively.
// Absolute path (from the crate root)
crate::network::server::start();
// Relative path (from where I am)
self::server::start();
super::other_thing(); // 'super' goes up one level (like cd ..)We’ll talk in depth about pub, use, and paths in the next article, but keep in mind that the position in the tree determines how you import things.
Practical rules
If you want a new module: Use mod name; in the parent file.
If the module is small: Create name.rs next to the parent.
If the module has children:
- Create a
name.rsfile. - Create a
name/folder. - Put the children inside the folder.
- Declare the children (
mod child;) insidename.rs.
Declare each module from its parent: A file is not incorporated into the tree just by existing. Its parent module must declare it with mod name; or through a mod name { ... } block.
:::