Sharing code across different files is a must for larger projects. Rust’s system of handling the exporting and importing of modules is a little different than in other languages. It was a bit tricky for me to understand at first, so here’s a quick rundown of how it works.
Understanding Rust’s Module System
There are two important things to understand about Rust’s module system. First, the module tree is not automatically mapped to the file structure, but is manually mapped by the programmer. Second, a module is not declared as a module in its own file but in its parent instead.
Let’s look at an example.
Here’s a basic file structure:
src/
├─ main.rs
├─ addition.rs
├─ module_user.rs
├─ mod.rs
├─ submodules/
│ ├─ subtraction.rs
│ ├─ mod.rs
Our end goal is to call a function that has been defined in addition.rs
, and a function that has been defined in submodules/subtraction.rs
. We want to call both of these functions in main.rs
.
Right now, without having done any module declaring, our module tree simply has one node (which is the root) called crate
. crate
is always at the top of our module tree, and any modules we declare are going to be children of crate
.
In addition.rs, we’ve defined a function called add()
that looks like this:
fn add(num1: u8, num2: u8) -> u16 {
num1 + num2
}
In subtraction.rs, we’ve defined a function called sub()
that looks like this:
fn sub(num1: i8, num2: i8) -> i8 {
num1 - num2
}
Declaring modules at the top level
Let’s start with calling add()
in main.rs.
The first thing we need to do is add the pub
keyword to the beginning of our function declaration, like this:
pub fn add(num1: u8, num2: u8) -> u16 {
num1 + num2
}
If we don’t add the pub
keyword, then we can’t call this function outside of this file at all. That’s because functions are private by default.
Now we need to declare this as a module in the parent of addition.rs
. Because it’s already at the top level of the file structure, this will be main.rs
. Here’s what main.rs
looks like after we do that:
mod addition;
fn main() {
println!("{:?}", addition::add(5, 10));
}
Now that we’ve declared addition
as a module, we can use it in our main.rs
file. Running this does as you’d expect: it just prints out the number 15. Also, now that we’ve declared it as a module, we can use it elsewhere as well. For example, in our module_user.rs
file we can use it like this:
pub fn print_addition() {
println!("{:?}", addition::add(10, 10));
}
After we declared the addition
module, our module tree now looks like this:
crate
├─ addition
Declaring nested modules
Now let’s do the work to call our sub()
function in main.rs
.
Just like before, we add pub
before our function declaration in subtraction.rs
:
pub fn sub(num1: i8, num2: i8) -> i8 {
num1 - num2
}
Now we declare our module in the parent, which is submodules
. To do that, we’ll declare the module in submodules/mod.rs
:
pub mod subtraction;
Notice that we’ve added the pub
keyword before the module declaration. If we didn’t do this, we wouldn’t be able to use subtraction
outside of submodules
. Also, the file this is done in must be called mod.rs
if we’re in a folder below src/
, otherwise Rust won’t know where to find it. (If it isn’t in a nested folder, it just needs to be in main.rs
, as we did earlier.)
Next, we need to declare submodules
as a module and call it in main.rs
. That’s going to look like this:
mod addition;
mod submodules;
fn main() {
println!("{:?}", addition::add(5, 10));
println!("{:?}", submodules::subtraction::sub(5, 10));
}
Running this does what you’d expect as well, it prints out 15, and then prints out -5. At this point, our module tree looks like this:
crate
├─ addition
├─ submodules
│ ├─ subtraction
The use
keyword
There’s something extra we can do to simplify our code a bit, and that’s using the use
keyword. I’ve made some changes to the main.rs
file. Here’s what it looks like now:
mod addition;
mod submodules;
use addition::add;
use submodules::subtraction::sub;
fn main() {
println!("{:?}", add(5, 10));
println!("{:?}", sub(5, 10));
}
The use
keyword simply allows us to use just the function name as opposed to the entire path whenever we want to call it inside main.rs
now. We can also use the use
keyword to use a different name for what we’re importing, like this:
mod addition;
mod submodules;
use addition::add as adder_function;
use submodules::subtraction::sub as my_sub;
fn main() {
println!("{:?}", adder_function(5, 10));
println!("{:?}", my_sub(5, 10));
}
We can use the use
keyword in files where we aren’t declaring the modules as well, like in our module_user.rs
file:
use addition::add;
pub fn print_addition() {
println!("{:?}", add(10, 10));
}
This was just an intro to using Rust modules across different files. There’s much more to Rust’s module system that I don’t have space in this post to cover. Hopefully, this is enough to get you started on it. With this info, you should be able to make your large projects much easier to work with.