home  >  software  >  rusts-complexity  >  modules

Exploring Rust’s Complexity

Modules

This one is a personal nemesis. It confused the heck out of me, until I sat down and told myself “you will work this out”. It is also a topic that I found confusing in other programming languages, especially in python. Good thing I gave up python (I can seriously recommend that).

Let’s look at what our example project looks like:

backyard
├── Cargo.lock
├── Cargo.toml
└── src
    ├── garden
    │   └── vegetables.rs
    ├── garden.rs
    └── main.rs

So how would we access functions or structs in src/garden.rs or src/garden/vegetables.rs? Let us imagine the following code:

// src/garden/vegetables.rs
#[derive(Debug)]
pub struct Asperagus {}
// src/garden.rs
pub mod vegetable;
// src/main.rs
use crate::garden::vegetables::Asperagus;

pub mod garden;

fn main() {
    let plant = Asperagus {};
    println!("I'm growing {:?}!", plant);
}

So how does this all relate? The line use crate::garden::vegetables::Asperagus can be broken down into use, which indicates that we are going to use code from another module or crate. crate refers to our root project. garden is a submodule in our project (src/garden.rs) and ...::vegetables::Asperagus refers to another submodule in garden and then finally the struct inside of our module (which has to be decalared pub - public - to be accessible from the outside).

pub mod garden in src/main.rs and pub mod vegetables in src/garden.rs is similar to an include statement in C/C++, since it just means “hey, please include the code from this module, thank you”.

There is also the super keyword. Let us look at the following example.

// src/garden/vegetables.rs
trait Veggie {
    // ...
}

pub mod popular {
    #[derive(Debug)]
    pub struct Asperagus {}
}

pub mod rare {
    #[derive(Debug)]
    pub struct Kohlrabi {}

    impl super::Veggie for Kohlrabi {
        // ...
    }
}

The super in the line impl super::Veggie for Kohlrabi { is necessary, because everything is scoped to the nearest module (which is the file, if an explicit mod is absent). So to go up “one level” or “one scope” we can use the super keyword. In this case we go to the parent of our module rare which is our current file.

Now we will add another file: src/lib.rs. We can treat lib.rs like the root of our crate, so we can use imports like this:

// src/garden.rs
#[derive(Debug)]
pub struct Vegetable {}
// src/lib.rs
pub mod garden;
// src/main.rs
pub use backyard::garden::Vegetable;

fn main() {
    // ...
}

Note, that in src/main.rs we refer to the root of the crate with the name of our directory, and not the keyword crate.

We can also export the Vegetable struct from lib.rs directly, by doing this:

// src/lib.rs
pub mod garden;

pub use garden::Vegetable;
// src/main.rs
pub use backyard::Vegetable;

fn main() {
    // ...
}

Accidental Or Unavoidable?

I think once you understand the module system in rust, it makes a lot of sense. It does to me now anyways. And compared to the module system of other languages it is certainly not worse.