Intro to Rust Modules
Tags: rust, import, module, system
Published: 2020-10-07 13:13:23

The Rust module system can be very confusing and difficult to understand when you are just starting out learning Rust. Something as simple as just putting a function in another file and importing it into your `main.rs` can send you on a frustrating search and leave you with even more questions.

In this article I will share some info that I hope will be helpful in understanding how to make better use of the module system and perhaps even start to embrace the functionality it gives you.

## Why not just import by file path?

I'm sure this is the approach many of us tried first and we were disappointed when it didn't work.

As an example, let's imagine we have some file called `cat.rs` and it contains the function `meow()`:
```rust
fn meow() {
    println!("Meow, Meow!");
}
```

And then we try to call `meow()` from `main.rs` like so:
```rust
fn main() {
    meow();
}
```

But Rust says no:
```
error[E0425]: cannot find function `meow` in this scope
 --> src/main.rs:2:5
  |
2 |     meow();
  |     ^^^^ not found in this scope
```

Ok. We forgot to make `meow()` public using the `pub` keyword, so we will fix that now and try again. Nope, same error.

What we might try next is something like this:
```rust
use cat;

fn main() {
    cat::meow();
}
```

Now we get this error:
```
error[E0432]: unresolved import `cat`
 --> src/main.rs:1:5
  |
1 | use cat;
  |     ^^^ no `cat` external crate
```

The mistake in the above code is that it assumes there is a 1 to 1 mapping between module name and our directory structure. However, Rust does not work like that, and I think this is the key point that helps in understanding how Rust modules work.

Rust's module paths are not tied directly to the filesystem. Rather we need to tell Rust a little more about the module structure before we can `use` our modules in our code. That is, think of the module structure as its own thing independent of the file system layout. First we need to tell Rust where each file belongs in the module hierarchy, and then we can import our module using the "module path" rather than the filesystem path.

This can seem cumbersome at first, but you will come to understand the reasons why it works this way.

To demonstrate why Rust doesn't just use file paths as the module path, let's look at an example with multiple modules in one file, like this `main.rs`:
```rust
mod cat {
    pub fn meow() {
        crate::dog::woof();
    }
}

mod dog {
    pub fn woof() {
        println!("Woof, Woof!");
    }
}

fn main() {
    cat::meow();
}
```
Obviously using separate modules for this is overkill but it demonstrates the way modules work.

Note that we need to use the `pub` keyword to allow each function to be visible to other modules (and you can also make things private to a module by omitting the `pub` keyword).

This will compile. And when we run it we get:
```
Woof, Woof!
```

So we can see that `mod cat` created a kind of module path with the function `meow()` nested below it, even though it is in the same file. And the module itself is nested below a global namespace called `crate`.

In this way we can declare different module namespaces in a much more flexible way than if we could only use file paths. For a simple example like this you may think it is unnecessarily complex but when you are writing a library and you want to provide a nice import structure for your API without needing to match that exact structure in your filesystem, this is a really nice feature. If you have ever used a library that let you do: `use somelib::prelude::*;` that is one of the benefits.

## What about different files?

Ok, so hopefully multiple modules in 1 file makes sense. What about importing functions from another file?

Let's return to our first example, with `meow()` declared in another file, named `cat.rs`.

The change we need to make within `main.rs` is to first tell Rust about the module `cat`.

The way we do this is by adding the following to our `main.rs`:
```rust
mod cat;
```

That tells Rust to look for either a `cat.rs` in the same directory OR alternatively a directory named `cat` with a `mod.rs` file within it (but you cannot have both a `cat.rs` file and a `cat` directory). We will get to directories shortly.

In our case we have `cat.rs` in the same directory, so now in our `main.rs` we can call the function within that module like so:
```rust
mod cat;

fn main() {
    cat::meow();
}
```
And that works!

## What about directories?

Previously I mentioned that `mod cat;` could point to a directory named `cat` containing a file called `mod.rs`.

Let's create this directory for our example, and then move `cat.rs` into that directory. Actually we will also rename `cat.rs` to `kitten.rs` to disambiguate (it's in a "child" module hehe).

So now we have a `main.rs` and a directory named `cat` containing two files, `mod.rs` and `kitten.rs`.

We *could* just put our `meow()` function in `mod.rs` and that would work just fine. However let's show how to import from another file within that directory.

In `mod.rs` we now need to tell Rust about `kitten.rs`, like this:

```rust
pub mod kitten;
```
(Note the `pub` keyword which makes the `kitten` module visible to the parent module as well)

But now our `main()` would need to call `meow()` like this:
```rust
mod cat;

fn main() {
    cat::kitten::meow();
}
```

What if instead we wanted to use the original import path in `main()`, while keeping our new file structure? Can we do that?

Yes we can! This is where Rust's module system hopefully starts to make more sense.

Try this in `mod.rs`:
```rust
mod kitten;
pub use kitten::meow;
```

And now we can once again call `cat::meow()` from within our `main()` function.

What this does is within `mod.rs` we tell Rust about another module, called `kitten` (which here points to `kitten.rs` in the same directory as `mod.rs`), which then becomes visible to the current module scope. But without the `pub` keyword, the nested module is invisible to the parent. Rather than make the entire module visible, we can import the `meow()` function from that module via `use` and we can make only this import public (visible to the parent module) with `pub use`.

We could even change the name of the function when re-exporting it, like this, in `mod.rs`:
```rust
mod kitten;
pub use kitten::meow as woof;
```

and now our `main()` would need to look like this:
```rust
fn main() {
    cat::woof();
}
```

I hope you are starting to see the flexibility that the Rust module system gives you over your entire namespace hierarchy. You can organise your files in a way that is convenient for you, and you can export your structs and modules in the way that makes more sense for the caller.

Sometimes these will be the same, and sometimes they won't. Rust lets you choose.

## Declaring the module hierarchy in one place

Putting module declarations in `mod.rs` files and so on is ok, but I personally think it can start to lead to too many module declarations scattered throughout the codebase and it can become difficult to follow.

In my own code I prefer to declare the entire module structure in `main.rs` (or `lib.rs` for library crates), which avoids the need for any `mod.rs` files while offering all of the same functionality.

For our above example, we can have our directory named `cat` and within it just the file `kitten.rs` (no `mod.rs` this time), and now our `main.rs` can look like this:

```rust
mod cat {
    mod kitten;
    pub use kitten::meow;
}

fn main() {
    cat::meow();
}
```

When I discovered this, that was the moment when I realised how powerful the Rust module system really is. This lets me describe my module structure all in one place, and then my whole crate can just refer to this structure to import things.

For example, let's add a new directory below `cat` called `activities` and containing a file called `play.rs`.
Here is `play.rs`:
```rust
pub struct CatToy {
    name: String
}

impl CatToy {
    pub fn new(name: &str) -> Self {
        Self { name: name.to_string() }
    }

    pub fn fetch(&self) {
        println!("Fetch the {}", self.name);
    }
}
```


We can describe this layout in `main.rs` like so:

```rust
mod cat {
    // We still have our imports from before.
    mod kitten;
    pub use kitten::meow;

    // And now we describe the additional structure.
    pub mod activities {
        pub mod play;
    }
}

fn main() {
    let ball = cat::activities::play::CatToy::new("ball");
    ball.fetch();
}
```

This is ok, but our import path is a bit long. We want to keep our files organised but we don't want the callers of these functions to have to worry about our directory structure. Or perhaps we want to modify our directory structure without modifying the import paths (so users of your crate don't need to update their code too).

So we can shorten the import path like this:
```rust
mod cat {
    mod kitten;
    pub use kitten::meow;

    mod activities {
        pub mod play;
    }

    pub use activities::play::*;
}

fn main() {
    let ball = cat::CatToy::new("ball");
    ball.fetch();
}
```

So you can see this greatly simplifies the use of the code for the caller.

## One last thing (well, several)

If you're importing (with `use`) something into another module outside of `main.rs`, you would need to add `crate::` to the start of the module path.

For example, if we wanted to use `CatToy` from another file:
```rust
use crate::cat::CatToy;

pub fn fetch_ball() {
    let ball = CatToy::new("ball");
    ball.fetch();
}
```

We didn't need to do that in the above examples because the `main()` function was already at the base level `crate` scope and all of the other modules were children of that scope.

Finally, if you just want to import and use a module somewhere without needing to re-export it to the parent module, you don't need `pub use`. Just `use crate::module::path;` will work fine. Only use `pub` when you need to make something visible to the parent scope.

## Further reading

Hopefully this article has helped to make the Rust module system a little easier to understand.

There is a lot more that I didn't cover.

If you want to read more about this, I highly recommend the documentation at [Rust By Example](https://doc.rust-lang.org/rust-by-example/mod.html).

Also the [Official Rust Book](https://doc.rust-lang.org/book/ch07-02-defining-modules-to-control-scope-and-privacy.html) has a good section on this as well.

I found a lot of the official documentation difficult to understand at first, but once I understood the basics of modules I was able to go back and re-read them and everything started to fall into place. It still took a lot of practice to get my head around it, but now I find it really nice and very powerful.