Rust’s Module System

mod and pub mod

Rust gives you a good API for modularizing your code. The mod keyword, simply put.

mod my_module {
    fn private_function() {
        println!("This is a private function");
    }

    pub fn public_function() {
        println!("This is a public function");
    }
}
// call the function
my_module::public_function();

Pretty straightforward. Functions are private by default unless explicitly marked as pub.

Nested modules follow the same behavior, which is why you see the pub mod combination of keywords.

mod my_module {
    mod nested_private_functions {
        fn nested_private_functions () {}
    }
    pub mod my_api {
        // ..public apis
        fn get_stuff() {
            // do stuff
        }
    }
}
// good
my_module::my_api::get_stuff();
// will fail to compile
my_module::nested_private_functions::nested_private_functions();

Inline modules vs. defined in a file.

Modules can be created by explicitly writing code inside of a mod { ... } block, as the current examples thus far have shown. This is called an inline module. Modules can also be created by moving the code into a separate file, then using the same mod declaration in the file you want to invoke the module from.

// cars.rs
pub struct Car {
    make: String,
    gas_level: i32,
    mileage: i32,
}

// lib.rs
mod cars; // <- pulls the code from cars.rs into scope for the current file, under the "cars" module

// inline module
pub mod my_module {
    fn do_stuff () { }
}

fn main() {
    my_module::do_stuff();
    let volvo = cars::Car::new(String::from("volvo"), 100); // use the code from the module
}

Nested modules

As mentioned earlier, Rust allows nested modules. These can be inline nested modules, or they can also be nested based on the file structure. You can access the parent module using the super keyword:

mod outer {
    pub mod inner {
        pub fn call_super() {
            super::parent_function(); // Call a function from the parent module
        }
    }

    pub fn parent_function() {
        println!("Called from parent module");
    }
}

One thing to note: in Rust, modules must be declared by the parent module.

use keyword

Looking at rust code, you will also see this use keyword getting called extensively. This keyword allows us to bring items from a module into scope, so to avoid writing long paths repeatedly.

mod cars;
use crate::cars::Car;

fn main() {
    let volvo = Car::new(String::from("volvo"), 100); // use the code from the module
}

You can also use this strategy to bring modules from external dependencies into scope as well:

use crate::cars::Car; // local module
extern crate serde; // external module

let json = r#"{"make": "Volvo"}"#;
let parsed: serde_json::Value = serde_json::from_str(json).unwrap();
let volvo = Car::new(...);

Please note: extern crate is usually not necessary since Rust 2018, usually we can just say crate. Some macros still require extern.

crate vs. mod

This can get a bit confusing since a “crate” sounds like it would be a package, or a module. In fact, a “crate” in rust is distinct from module.

  • crate is the top-level compilation unit in Rust. A Rust program is a crate.
  • module is a way to organize and structure code within a crate

mod.rs

Rust does not actually explicitly use files for modules like JS or python. A module is not necessarily 1:1 with a file. mod.rs is an optional convention in Rust used to define a module in a directory without needing to specify an additional file name. When Rust encounters a directory that is declared as a module, it looks for mod.rs in that directory as the entry point for the module’s implementation. This approach allows organizing related code and modules within a directory structure while keeping the code modular and maintainable.

So, in other words, you can do this:

my_project/

├── src/
│   ├── main.rs
│   ├── lib.rs
│   ├── my_module/
│   │   ├── mod.rs
│   │   ├── sub_module.rs

Then in main.rs, use stuff from my_module::sub_module

Summary

So, to conclude, some cliffnotes:

  • mod defines a module.
  • Modules can be inline or in separate files.
  • Public items are exposed with pub.
  • Use use to bring items into scope.
  • Paths can be absolute or relative using crate, self, or super, and can also refer to external dependencies.
end of storey Last modified: