DEV Community

Cover image for Understanding Rust modules
Dandy Vica
Dandy Vica

Posted on

Understanding Rust modules

If you ever fought against Rust modules, you probably already googled for terms such as "rust modules" or "rust modules structure". And you probably stumbled upon the Rust book. But after reading the articles, you're maybe in a situation were you still don't grab the whole idea, and according to the new article called Path Clarity:

... why it's so confusing to many: while there are simple and consistent rules defining the module system, their consequences can feel inconsistent, counterintuitive and mysterious.

For modules defined inside a source .rs file, that's OK. But if you're a seasoned developer, you're used to break down your code into different source files.

I wrote this article, with a simple example, to better understand the Rust module system. I'm referring to the last version of Rust called Rust 2018.

So let's create a simple project structure using cargo, with a hierarchical structure

$ cargo new rust_modules --bin
Enter fullscreen mode Exit fullscreen mode

Let's create a simple module hierarchical structure based on maths:

// main.rs
mod math {
    pub mod arithmetic {
        pub fn add(x: i32, y: i32) -> i32 {
            x + y
        }
        pub fn mul(x: i32, y: i32) -> i32 {
            x * y
        }
    }

    pub mod trigonometry {
        pub mod ordinary {
            pub fn sin(x: f32) -> f32 {
                x.sin()
            }
            pub fn cos(x: f32) -> f32 {
                x.cos()
            }
        }

        pub mod hyperbolic {
            pub fn sinh(x: f32) -> f32 {
                (x.exp() - (-x).exp()) / 2f32
            }
            pub fn cosh(x: f32) -> f32 {
                (x.exp() + (-x).exp()) / 2f32
            }
        }
    }
}

fn main() {
    use math::arithmetic::{add, mul};
    use math::trigonometry::hyperbolic::{cosh, sinh};
    use math::trigonometry::ordinary::{cos, sin};
}
Enter fullscreen mode Exit fullscreen mode

This code compiles because it's located in the same source file. Things are getting more complicated if you want to split the module into different source files:

// src/math.rs or src/math/mod.rs
pub mod arithmetic {
    pub fn add(x: i32, y: i32) -> i32 {
        x + y
    }
    pub fn mul(x: i32, y: i32) -> i32 {
        x * y
    }
}

pub mod trigonometry {
    pub mod ordinary {
        pub fn sin(x: f32) -> f32 {
            x.sin()
        }
        pub fn cos(x: f32) -> f32 {
            x.cos()
        }
    }

    pub mod hyperbolic {
        pub fn sinh(x: f32) -> f32 {
            (x.exp() - (-x).exp()) / 2f32
        }
        pub fn cosh(x: f32) -> f32 {
            (x.exp() + (-x).exp()) / 2f32
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

You can put the internal of the math module into a single file in src/math.rs (filename has the same name of the module without extension) or in src/math/mod.rs. Then the
main.rs becomes:

// math module internals is either in src/math.rs or src/math/mod.rs
mod math;

fn main() {
    use math::arithmetic::{add, mul};
    use math::trigonometry::hyperbolic::{cosh, sinh};
    use math::trigonometry::ordinary::{cos, sin};
}
Enter fullscreen mode Exit fullscreen mode

You can even use the new path attribute to move your source code wherever you want:

// the source code containing the math module is moved to /tmp/math.rs
#[path="/tmp/math.rs"]
mod math;

fn main() {
    use math::arithmetic::{add, mul};
    use math::trigonometry::hyperbolic::{cosh, sinh};
    use math::trigonometry::ordinary::{cos, sin};
}
Enter fullscreen mode Exit fullscreen mode

So using the mod.rs technique, you could breakdown the math module into several source files:

├── main.rs
└── math
    ├── arithmetic.rs
    ├── mod.rs
    └── trigonometry
        ├── hyperbolic.rs
        ├── mod.rs
        └── ordinary.rs
Enter fullscreen mode Exit fullscreen mode

with the following mod.rs files:

// src/math/mod.rs
pub mod arithmetic;
pub mod trigonometry;
Enter fullscreen mode Exit fullscreen mode
// src/math/trigonometry/mod.rs
pub mod hyperbolic;
pub mod ordinary;
Enter fullscreen mode Exit fullscreen mode

I should say this is not the most straightforward module strategy, but once you get acquainted to it, it feels more natural. So the key takeaways are:

  • the mod & use keywords are like import or require in popular languages
  • a module named mymod reflects either a filename similar to mymod.rs or mymod/mod.rs

Photo by Timo Volz on Unsplash

Top comments (3)

Collapse
 
elasticrash profile image
Stefanos Kouroupis

I think the confusion starts when you want to use modules within each other but don't want to have like 10 layer deep structure. That's why when I use rust I use a more flatten out structure. By creating standalone modules in the code root directory and then use the use the use create

i.e.

use crate::functions::stats::*;
use crate::functions::analyse::*;
Enter fullscreen mode Exit fullscreen mode
Collapse
 
dandyvica profile image
Dandy Vica

Thanks for your reply.

I like things to be tidy, that's why I much prefer a hierarchical structure. But that's just a personal opinion.

Collapse
 
arunkumar413 profile image
Arun Kumar

Why are we not using the use crate::.. syntax to get the access the modules?