Rust's official documentation is formidable, but the explanation about file and directory structuring can seem confusing. In this article, I intend to show it in a simple way - without going deep into all possibilities and details. Let's check it out!

In Rust, components like fn, struct, enum, trait, etc. are called items. Items in turn can be grouped into modules mod. Modules are also considered items, so they form a hierarchical structure.

Creating Another Source File

Let's create an empty program, enter the directory and open vs code:


    cargo new struct-dir
    cd struct-dir
    code .

When we create a new project, we have the following structure:

directory and file structure

In main.rs there's the Hello World function:


    fn main() {
        println!("Hello, world!");
    }

Cool, now let's create a new function say_hello and call it from main():


    fn main() {
        say_hello();
    }

    fn say_hello() {
        println!("Hello, world!");
    }

Suppose we want to put the say_hello method in a file called services.rs. The structure will look like this:

directory and file structure

When compiling now, we get the error:


    error[E0425]: cannot find function `say_hello` in this scope
    --> src/main.rs:2:5
    |
  2 |     say_hello();
    |     ^^^^^^^^^ not found in this scope

To solve this, we need to do a few more things. First, we have to make the say_hello function public, that is, visible to functions outside its source code. To do this, just add the pub prefix before fn:


    pub fn say_hello() {
        println!("Hello, world!");
    }

Second, we have to indicate that the services.rs file will be a module. To do this, we have to insert at the beginning of the main.rs file the line mod services;

Now we can inform the module name in the function prefix, using the :: delimiter. The code will look like this:


    mod services;

    fn main() {
        services::say_hello();
    }

Creating a Directory Module

Now let's suppose we want to create a directory called handlers with a file user_handler.rs inside. The structure will be:

src/
β”œβ”€β”€ main.rs
β”œβ”€β”€ services.rs
└── handlers/
    β”œβ”€β”€ mod.rs
    └── user_handler.rs

Notice that we need a mod.rs file inside the handlers directory. This file serves as the "index" of the module, declaring what's public.

Content of handlers/mod.rs:


    pub mod user_handler;

Content of handlers/user_handler.rs:


    pub fn handle_user() {
        println!("Handling user!");
    }

In main.rs:


    mod services;
    mod handlers;

    fn main() {
        services::say_hello();
        handlers::user_handler::handle_user();
    }

Using use for Shorter Paths

To avoid writing the full path every time, we can use use:


    mod services;
    mod handlers;

    use handlers::user_handler::handle_user;

    fn main() {
        services::say_hello();
        handle_user(); // Now we can call directly
    }

Modern Alternative: Using File Name as Module

Since Rust 2018 edition, you can also use the file name directly instead of mod.rs. So instead of:

handlers/
β”œβ”€β”€ mod.rs
└── user_handler.rs

You can have:

handlers.rs          # Contains: pub mod user_handler;
handlers/
└── user_handler.rs

Both approaches work, but the mod.rs approach is more traditional and still widely used.

Conclusion

Organizing Rust code into modules and files is essential for maintainable projects. The key points to remember are:

  1. Use mod to declare modules
  2. Use pub to make items visible outside their module
  3. Use mod.rs or a file with the directory name to create directory modules
  4. Use use to bring items into scope for shorter paths

With these basics, you can structure Rust projects of any size in a clean and organized way.