\ ## TL;DR * š”Ā Easy and simple explanation of Rust's modules across different files. * š¤æĀ We'll deep dive into a real-world example to explore the module system. * šĀ Tons of diagrams to help you understand. --- The module system in Rust can be confusing for developers coming from other languages. It took me a while to understand it so I want to share with you how it works and how to organize your program across multiple files with ease. Let's go. ## Rust Modules Across Files Rust requires the developers to manually build module trees. The way to do it is by declaring modules with theĀ **mod**Ā keyword. The module tree starts with the crate root, usuallyĀ *src/lib.rs*Ā for a library crate orĀ *src/main.rs*Ā for a binary crate. The Rust compiler will first look in the crate root for modules to compile. Let's say you want to import a module "a" in a binary crate, you can declare the module like this: \ main.rs ```rust mod a; fn main() { /* do amazing things */ } ``` The compiler will look for the module in theĀ *src*Ā directory in the following places: \ InĀ *src/a.rs* ```rust . āāā Cargo.lock āāā Cargo.toml āāā src Ā Ā āāā a.rs Ā Ā āāā main.rs ``` \ Or inĀ *src/a/mod.rs* ```rust . āāā Cargo.lock āāā Cargo.toml āāā src āāā a ā āāā mod.rs āāā main.rs ``` \ By declaringĀ **mod a**Ā inĀ *main.rs*, you've built a module tree like this:  ## Rust Submodules Across Files Within a module, you can create submodules to further organize your code. Let's say you want to declare module "b" and "c" in module "a": \ /src/a/mod.rs ```rust mod b; mod c; ``` \ The compiler will look for the submodules inĀ *src/a*Ā directory: \ ```rust . āāā Cargo.lock āāā Cargo.toml āāā src āāā a ā āāā b.rs ā āāā c.rs ā āāā mod.rs āāā main.rs ``` \ Now you've build a tree like this: \  ## Visibility with "pub" By default, all theĀ __[items](https://doc.rust-lang.org/reference/items.html)__Ā in a module are private. They are only visible by the items in the same module. \ src/a/mod.rs ```rust mod b; mod c; fn do_a() {} // only the other functions in module a can use it // it's not visible to main.rs ``` In order for its parent modules to have access to the functionĀ *do_a*, we need to add the key wordĀ **pub**. \ src/a/mod.rs ```rust pub fn do_a() {} // now it's visible to main.rs ``` We can accessĀ *do_a*Ā using theĀ __[path qualifier](https://doc.rust-lang.org/reference/paths.html)Ā *[::](https://doc.rust-lang.org/reference/paths.html)*__. \ src/main.rs ```rust mod a; fn main() { a::do_a(); } ``` \ We can use the same pattern for submodules. \ src/a/b.rs ```rust pub fn do_b() {} // visible to module "a" and all the submodules of module "a" ``` \ By addingĀ **pub**Ā toĀ *do_b*, the function now is accessible to module "a". \ src/a/mod.rs ```rust mod b; mod c; pub fn do_a { b::do_b(); } ``` \ *do_b*Ā is also accessible to the submodules of module "c". You can access it with either the absolute or relative path. \ src/a/c.rs ```rust pub fn do_c { crate::a::b::do_b(); // absolute path super::b::do_b(); // relative path } ``` ## Re-exporting Items An item of a submodule is not accessible to a non-parent module. For example, we can try to accessĀ *do_b*Ā inĀ *main.rs* \ src/main.rs ```rust mod:a; fn main() { a::b::do_b(); // ^^^^ function `do_b` is private } ``` \ You'll see an error message sayingĀ *do_b*Ā is private. That's becauseĀ *do_b*Ā is only accessible within module "a" so far. To make it visible to the crate root, We need to re-export it by addingĀ **pub**Ā to the module "b" declaration from module "a". \ src/a/mod.rs ```rust pub mod b; // --snip-- ``` ## The "use" Declaration TheĀ **__[use](https://doc.rust-lang.org/reference/items/use-declarations.html)__**Ā declaration can help you shorten the path when accessing an item in another module. For example, we can refactor the module "a": \ src/a/mod.rs ```rust mod b; mod c; use b::do_b; use c::do_c; pub fn do_a { do_b(); do_c(); } ``` \ It creates a local name binding to its path forĀ *do_b*Ā andĀ *do_c*.Ā **use**Ā is very useful for long paths. ## A Real World Example To demonstrate the Rust's module system, I created a simple CLI calledĀ **affme**, short for "affirm me". \  \ **affme**Ā is an self-affirmation generator. The CLI takes in a name as a parameter and displays a randomized affirmation. \ > The demo isĀ __[available on GitHub](https://github.com/DawChihLiou/affme)__. Feel free to take a look at the repo and try it out⨠\ The code design is straightforward: \  \ In the "format" block, \ * It takes a user input, * concatenates the input with a random affirmation and a random emoji, * applies a random font color to the concatenated affirmation, * and finally outputs the affirmation. * \ To showcase the module system across files, I design the module tree as following: \  \ A few things worth mentioning: \ * This package has two crates, one binary and one library. I use the library crate to encapsulate the implementation and the binary crate to execute the CLI. * In the library crate rootĀ *src/lib.rs*, it accesses functions from theĀ *affirmation*Ā andĀ *formatter*module. * TheĀ *affirmation*Ā module and both of the submodules in theĀ *formatter*Ā module are using the same function in theĀ *random*Ā module to randomly pick an item. Because theĀ *affirmation*module andĀ *formatter*Ā submodules are in different branches of the tree, we need to declare theĀ *random*Ā module in the common ancestor of the module tree. \ In the file system, it looks like this: \ ```javascript . āāā Cargo.lock āāā Cargo.toml āāā src āĀ Ā āāā affirmation.rs āĀ Ā āāā formatter āĀ Ā āĀ Ā āāā color.rs āĀ Ā āĀ Ā āāā emoji.rs āĀ Ā āĀ Ā āāā mod.rs āĀ Ā āāā lib.rs āĀ Ā āāā main.rs āĀ Ā āāā random.rs āāā target ``` \ Let's dive into the library crate root to see how the code is structured. \ src/lib.rs ```rust mod affirmation; mod formatter; mod random; use affirmation::Affirmation; use formatter::format; pub fn affirm(name: &str) -> String { let affirmation = Affirmation::new().random(); format(affirmation, name) } ``` \ Here you can see the module declarations on the top. You can also find theĀ *use*Ā declarations to create the local name binding forĀ *Affirmation*Ā andĀ *format*. The random module is straightforward: \ src/random.rs ```rust use rand::Rng; pub fn pick<'a, T: ?Sized>(items: &[&'a T]) -> &'a T { let random_index: usize = rand::thread_rng().gen_range(0..items.len()); items.get(random_index).unwrap() } ``` \ It has a publicĀ *pick*Ā function that returns a random item from an array slice. I use the function to pick random affirmations, emojis, and colors. Let's take a look atĀ *affirmation*Ā module as an example: \ src/affirmation.rs ```rust use crate::random; #[derive(Debug)] pub struct Affirmation<'a> { affirmations: [&'a str; 6], } impl<'a> Affirmation<'a> { pub fn new() -> Self { let affirmations = [ "You're beautiful", "You're awesome", "You're wonderful", "You've got this", "You can do all things", "Go get it", ]; Affirmation { affirmations } } pub fn random(&self) -> &'a str { random::pick(&self.affirmations) } } ``` \ You can see theĀ *use*Ā declaration for theĀ *random*Ā module. TheĀ *affirmation*Ā module is able to access theĀ *random*Ā module because theĀ *random*Ā module was declared in the library crate root. I use theĀ *pub*Ā keyword on theĀ *Affirmation*Ā struct and its functions so that the crate root has visibility over them. \ You can find the same coding pattern in theĀ *emoji*Ā andĀ *color*Ā submodule. \ To bring it all together, let's take a look at theĀ *format*Ā module. \ src/formatter/mod.rs ```rust mod color; mod emoji; use color::Color; use colored::*; use emoji::Emoji; pub fn format(affirmation: &str, name: &str) -> String { let emoji = Emoji::new(); let color = Color::new(); let phrase = format!("{}, {} {}", affirmation, name, emoji.random()) .color(color.random()) .bold() .to_string(); format!( "{}\n{}\n{}\n{}\n{}", "*".repeat(phrase.len() + 2).magenta(), format!("*{}*", " ".repeat(phrase.len())).magenta(), format!(" āļø ...{} ", phrase,), format!("*{}*", " ".repeat(phrase.len())).magenta(), "*".repeat(phrase.len() + 2).magenta() ) } ``` \ It bringsĀ *color*Ā andĀ *emoji*Ā submodules in scope so we can concatenate the full affirmation with random emoji and random font color. ## Final Thoughts Rust Modules across multiple files is a little different from other languages but once you understandĀ **mod**,Ā **use**, andĀ **pub**, the module design becomes easier and intentional. \ **Rust Module Cheat Sheet** \ * A module tree starts from the crate root. * UseĀ **mod**Ā to build your tree with modules and submodules. * UseĀ **pub**Ā to make module items visible to the parent module. * You can re-export withĀ **pub mod**Ā orĀ **pub use**. ## References * __[Book: Defining Modules to Control Scope and Privacy - The Rust Programming Language](https://doc.rust-lang.org/book/ch07-02-defining-modules-to-control-scope-and-privacy.html)__ * __[Book: Use declarations - The Rust Reference](https://doc.rust-lang.org/reference/items/use-declarations.html)__ * __[Book: Items - The Rust Reference](https://doc.rust-lang.org/reference/items.html)__ * __[Book: Modules - The Rust Reference](https://doc.rust-lang.org/reference/items/modules.html)__ * __[Book: Paths - The Rust Reference](https://doc.rust-lang.org/reference/paths.html)__ * __[Article: How to Use Rust Modules Across Different Files - Casey Falkowski](https://spin.atomicobject.com/2022/01/24/rust-module-system/)__ * __[GitHub: affme repository](https://github.com/DawChihLiou/affme)__ --- This article was originally posted on [Daw-Chihās website](https://dawchihliou.github.io/articles/easiest-way-to-understand-rust-modules-across-multiple-files). \n \n \