Skip to main content

Understanding Rust Modules

Β· 3 min read

Coming from JavaScript world, understanding Rust modules was a bit challenging. This document aims to describe the Rust modules system via common examples.

There are 3 important keywords (source):

  • mod declares a module
  • pub exposes an item by a single level
  • use pulls things in from an absolute path to the current scope

I recommend creating experiments package if you want to try these examples and run them via:

cargo run --bin experiments

Sources:

In-place (in-file) modules​

β”œβ”€β”€ Cargo.toml
└── src
└── main.rs

This way you can define the file inside one file:

example/main.rs
mod my_module {
pub fn test() {
println!("OK πŸ‘Œ");
}
}

fn main() {
my_module::test();
}

Module in a separate file​

The module my_module can be moved into separate file like so:

β”œβ”€β”€ Cargo.toml
└── src
β”œβ”€β”€ main.rs
└── my_module.rs

In this case main declares the module without the body:

example/main.rs
mod my_module;

fn main() {
my_module::test();
}

Our module lives in a separate file without the mod declaration:

example/my_module.rs
pub fn test() {
println!("OK πŸ‘Œ");
}

What happens if we move the module into separate file including the module declaration like in the following example?

example/whatever.rs
pub mod my_module {
pub fn test() {
println!("OK πŸ‘Œ");
}
}

First, we have to declare the module public (see the pub keyword). Secondly, the module would have to be used like so:

example/main.rs
mod whatever;

fn main() {
whatever::my_module::test();
}

Module in a separate directory​

β”œβ”€β”€ Cargo.toml
└── src
β”œβ”€β”€ main.rs
└── my_module
└── mod.rs

It's possible to decompose the first example a bit differently by introducing a new directory with special mod.rs file:

example/main.rs
mod my_module;

fn main() {
my_module::test();
}

And the actual module:

example/my_module/mod.rs
pub fn test() {
println!("OK πŸ‘Œ");
}

Re-exporting submodule​

So far, we used keywords mod to declare the module and pub to make it visible. Let's try to play around with use keywords. Imagine the following structure:

β”œβ”€β”€ Cargo.toml
└── src
β”œβ”€β”€ main.rs
└── my_module
β”œβ”€β”€ mod.rs
└── my_submodule.rs

File main.rs would use the submodule like so:

example/main.rs
mod my_module;

fn main() {
my_module::my_submodule::test();
}

File my_module/mod.rs simply re-exports the submodule:

example/my_module/mod.rs
pub mod my_submodule;

And finally the submodule:

example/my_module/submodule.rs
pub fn test() {
println!("OK πŸ‘Œ");
}

We could modify it in several ways. For example main.rs could import the test method directly like so:

example/main.rs
mod my_module;

use my_module::my_submodule::test;

fn main() {
test();
}

What if we would like to hide the fact that there is a submodule and use my_module::test directly like in the following example?

example/main.rs
mod my_module;

fn main() {
my_module::test();
}

This is easily achievable by re-exporting the submodule via pub use keywords:

example/my_module/submodule.rs
mod my_submodule;

pub use my_submodule::test;

Reexporting with self​

pub use self::implementation::api;

mod implementation {
pub mod api {
pub fn f() {}
}
}

Any external crate referencing implementation::api::f would receive a privacy violation, while the path api::f would be allowed.

When re-exporting a private item, it can be thought of as allowing the "privacy chain" being short-circuited through the reexport instead of passing through the namespace hierarchy as it normally would.

Source: https://doc.rust-lang.org/reference/visibility-and-privacy.html