loading...

Learning Rust - Organizing our code with modules

brunooliveira profile image Bruno Oliveira ・3 min read

Introduction

In the previous post in this series, we looked at Cargo, and how the build system and package manager for Rust can help developers setting up more complex projects and how to create libraries and binary sources with it.
Now, we will see how to actually create and wire modules in our main application code, to make our future designs more modular and easy to maintain.

Organize code with Modules

In order to ensure that code stays clean and maintainable, we can use modules.
The analogy I draw from Rust modules is slightly similar to Java packages:

It's a way of ensuring that code that is linked together by structure or functionality, can be grouped under a single, cohesive unit that can be referenced later as needed.

We can have multiple modules under the src directory and these can be referenced in other locations by ensuring that the module is referenced at the file where we want to use its functions. But first, let's see how to create a simple module.

Creating a module

Let's say that we want to create and work on a module to support a series of mathematical operations.

We will call it math_utils and it will contain a series of utility functions that we will be able to re-use in our "client code", so we can perform some mathematical operations as we see fit.

In order to adhere to some convention, I will name the actual rust source file the same as our module, so, under the src dir of our project, we can create a file called math_utils.rs and we can add the following code:

pub mod math_utils {
    pub fn square(value: i32) -> i32 {
        return value*value
    }
    pub fn apply_to_vec(vec: Vec<i32>, function: fn (i32) -> i32) -> Vec<i32> {
        return vec.iter().map(|elem: &i32| function(*elem)).collect()
    }
}

Let's see how it all works.

mod is the keyword that indicates we are creating a new module, and pub is a visibility modifier that simply states that our module can be referenced from other locations.
The same keyword is applied to the functions we are defining inside the module, and, omitting it makes it private, which means it can't be used outside of its scope, resulting in error E0603 which tells us we tried to use a private item outside of its scope (which will be our module in this case).

Inside a module, we can define multiple functions that will be part of the module's code and provide some functionality that we can use.

For this very simple module, we have defined two very simple functions:

  • A basic square function receiving an integer and returning its square.

  • A function that receives a vector and a new function and applies the function to each element of the vector.

Both of these functions are marked as public which means that we can and intend to use them outside of the scope of our module.

Let's see how we can link it to client code.

Using our module

Now that we have created a basic module, we can use it. For simplicity, let's call this module from our main code, defined in the file, main.rs.

In order to do that, we add the following on top of our main.rs file:

mod math_utils;
use math_utils::math_utils::square;
use math_utils::math_utils::apply_to_vec;

First we expose the module in our main code with mod math_utils that will allow us to declare which functions of our module we actually want to use. We do it with use statements as seen above.

Then in the main code we simply invoke our functions:

fn sum2(x: i32) -> i32 {
    return x+2;
}

fn main() {
    let v = square(23);
    println!("Square is {}",v);
    let v2 = apply_to_vec(vec![1, 2, 3], sum2);
    println!("Operation result is {:?}",v2);
}

And this is how we can use modules in Rust!

Stay tuned for more!

Discussion

pic
Editor guide