loading...
Cover image for Understanding Rust modules

Understanding Rust modules

dandyvica profile image Alain Viguier ・3 min read

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

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};
}

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
        }
    }
}

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};
}

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};
}

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

with the following mod.rs files:

// src/math/mod.rs
pub mod arithmetic;
pub mod trigonometry;
// src/math/trigonometry/mod.rs
pub mod hyperbolic;
pub mod ordinary;

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

Posted on by:

dandyvica profile

Alain Viguier

@dandyvica

Senior software engineer & sysadmin, technical architect, Linux expert. Always willing to learn new stuff.

Discussion

pic
Editor guide
 

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::*;
 

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.