Rust Modules Tutorial

In this Rust tutorial we learn how modules allow us to separate our code into smaller external sections that can be imported into our files.

We cover how to define external modules, import them into a document and access the code inside as well as public and private access modifiers.

Here's a table of contents of what you'll learn in this lesson:
(click on a link to skip to its section)

Let's jump right in.

What is a module

Rust allows us to organize our programs by separating our code into smaller sections of specialized logic. For example, we can separate all mathematical related code, or all networking related code it its own section.

If you’re familiar with a language like C#, a module is similar to a namespaceOpens up in a new page .

Multiple modules are compiled into units called crates, like binary crates, or library crates.

  • A binary crate is an executable project and must have a main() function as entry point for the application.
  • A library crate is a group of components that can be reused in any project, such as a set of mathematical or networking modules. A library crate does not have a main() function because it’s not an executable project on its own.

The site crates.ioOpens up in a new page hosts a massive collection of third-party crates that can be downloaded into your project. These crates include specialized modules for operations like working with regex, crypto, http and more.

How to define a module

Modules are typically defined in external files and imported into the main document where we then have access to everything inside that module.

Inside Eclipse, in the Project Pane, right-click on the src folder and select New > File. Name the file my_module.rs.

The file name will double as our module name. Open the my_module.rs file and add the following code.

Example: my_module.rs
pub fn log_mod() {

    println!("my_module is imported and ready to go");
}

In the example above, we have a simple function called log_mod that will print a message to the console.

In front of the function definition is the keyword pub . This is an access modifier and means that the function is available to be used outside of the my_module.rs file.

How to import a module

Now that we have a module, we can import it into the main.rs file.

To import the module, we use the keyword mod , followed by the file name without the .rs extension.

Example: main.rs
mod my_module;

fn main() { }

When a module is imported, it allows us to use any code that’s marked as public in the file we’re importing the module from.

Note that the module must be imported before we use any of its code. Modules are usually imported near the top of the document.

How to access public code from within a module

To access code from within an imported module, we use double colon notation.

We write the module name (file name without .rs extension), followed by two colons and the code we want to use.

Example: main.rs
mod my_module;

fn main() {

    my_module::log_mod();

}

When we run the example above, it prints out the message from the log_mod() function in the my_module.rs file.

Module access modifiers (Private vs. Public)

Rust allows us to encapsulate a module with access modifiers. Encapsulation is where we hide unnecessary implementation details from the rest of the program.

As an example, consider a restaurant.

In a restaurant, we don’t need to worry how the food is prepared behind the scenes, so access to the kitchen is restricted to only staff members that need access to it. It’s private.

The server is available to be interacted with publicly, but still has access to the kitchen to bring us anything we need from it.

As a programmatic example, consider a module that translates a document into another language.

The module would encapsulate the inner details like loading, manipulating and closing the file. We don’t have to worry about it, it can be done in the kitchen, privately.

We need the translated text to be publicly available to us so that we can use it. So, only a small part of the module actually needs to be public.

Encapsulation also provides us with data protection, we can’t accidentally change how the file is manipulated from outside the module if it’s properly protected.

By default, everything inside a module will be private, we don’t need to do anything special to it. When we do want something inside the module to be publicly available, we just add the keyword pub .

Let’s see a simplified version of the document translator in action.

Example: my_module.rs
pub fn serve_file()->String {

    return translate_file();
}

fn load_file() {
    println!("File loaded");
}

fn close_file() {
    println!("File closed");
}

fn translate_file()->String {

    load_file();
    println!("Translating...");
    close_file();

    return String::from("\nThis is the translation");
}
Example: main.rs
mod my_module;

fn main() {

    let result = my_module::serve_file();

    println!("{}", result);
}

In the example above, we only have access to the function that serves the finished file. We don’t need access to the “behind-the-scenes” functions like load_file or close_file.

Our document is protected and our main code is light.

Summary: Points to remember

  • A module allows us to separate our code into smaller external sections that can be imported into a file.
    • Modules are compiled into binary crates or library crates.
  • To define a module we add code to an external .rs file. The file name will act as the module name.
  • When we import a module, we specify the file name without the .rs extension.
  • Access modifiers like pub restrict access to code inside a module.
    • By default, code inside a module is private. If we want it to be public, we have to mark it with the pub keyword.
    • Public code can be accessed outside of the module with double colon notation.