braindead
4 min readDec 13, 2022

--

File Hierarchy
Layout of Rust crate, the official package manager for Rust, defines some conventions for the layout of a Rust crate.
Convention of Rust crate
Cargo.toml and Cargo.lock are stored in the root of your package (package root).
Source code goes in the src directory.
Default library file is src/lib.rs .
Default executable file is src/main.rs .
Other executables can be placed in src/bin/ .
Benchmarks go in the benches directory.
Examples go in examples directory.
Integration tests go in the tests directory.
If a binary, example, bench, or integration test consists of more than one source file, then place a main.rs file along with the extra modules within the subdirectory of the src/bin , examples , benches , or tests directory. The name of the executable directory will be the name of the directory.
By following this standard layout, you will be able to use cargo commands cargo new to set up a new executable project or cargo new — lib to set up a new library project.
Documentation for libraries is written in documentation comments by following convention /// before any item and //! for documenting parent items. Also, the licence is usually put at root.
Unit tests are written in the same module as the function we are testing. Usually put in an inner module.
Modules
Let’s simulate a real-world project with this file structure:
File system Tree
These are the various ways in which we should be able to consume our modules:
main.rs => configs.rs
main.rs => routes/sports_route.rs
main.rs => routes/user_route.rs => models/user_model.rs
Let’s start with the first example — import config.rs in main.rs.
Rust
// main.rs
fn main() {
println!(“main”);
}
Rust
// config.rs
fn print_config() {
println!(“config”);
}
The first mistake that everyone makes is just because we have files like config.rs, sport_route.rs etc, we think that these files are modules and we can import them from other files.
Here’s what we see (file system tree) and what compiler sees (module tree):
File System TreeModule System Tree
Surprisingly, the compiler only sees the crate module which is our main.rs file. This is because we need to explicitly build the module tree in Rust — there’s no implicit mapping between file system tree to module tree.
To add a file to the module tree, use the mod keyword to declare it as a submodule. The next thing that people get wrong is that they believe we declare a file as a module in the same file. However, we must declare this in a separate file! Because the module tree only contains main.rs, let’s declare config.rs as a submodule in main.rs.
The mod keyword has this syntax:
mod my_module;
Here, the compiler looks for my_module.rs or my_module/mod.rs in the same directory.
File System Tree
Or
File System tree
Since main.rs and config.rs are in the same directory, let’s declare the config module as follows:
Rust
// main.rs
// +
mod config;

fn main() {
// +
config::print_config();
println!(“main”);
}
Rust
// config.rs
fn print_config() {
println!(“config”);
}

We’re accessing the print_config function using the :: syntax.
Here’s how the module tree looks like:

The config module has been declared successfully! However, this is insufficient to call the print config function within config.rs. Because almost everything in Rust is private by default, we must use the pub keyword to make the function public:
The pub keyword makes things public.
Rust
// main.rs
mod config;

fn main() {
config::print_config();
println!(“main”);
}
Rust
// config.rs
//- fn print_config() {
+ pub fn print_config() {
println!(“config”);
}
This is now functional. We successfully invoked a function defined in another file!
super
If our file organisation has multiple directories, the fully qualified name becomes too long. Let’s say we want to call print_sport_route from print_user_route for whatever reason. These are located under the paths crate::routes::sport_route and crate::routes::user_route.
We can call it by using the fully qualified name crate::routes::sport_route::print_sport_route() but we can also use a relative path super::sport_route::print_sport_route();. Notice that we’ve used super to refer to the parent scope.
The super keyword in module path refers to the parent scope.
Rust
pub fn print_user_route() {
crate::routes::sport_route::print_sport_route();
// can also be called using
super::sport_route::print_sport_route();

println!(“user_route”);
}

use
It would be tedious to use the fully qualified name or even the relative name in the above examples. In order to shorten the names, we can use the use keyword to bind the path to a new name or alias.
The use keyword is used to shorten the module path.
Rust
pub fn print_user_route() {
crate::models::user_model::print_user_model();
println!(“user_route”);
}

External modules
Dependencies added to Cargo.toml are available globally to all modules inside the project. We don’t need to explicitly import or declare anything to use a dependency.
External dependencies are globally available to all modules inside a project.
For example, let’s say we added the rand crate to our project. We can use it in our code directly as:
Rust
pub fn print_sport_route() {
let random_number: u8 = rand::random();
println!(“{}”, random_number);
println!(“sport_route”);
}
Summary
The module system is explicit, there is no 1:1 mapping with the file system.
We declare a file as a module in its parent, not in itself.
The mod keywords are used to declare submodules.
We need to explicitly declare functions, structs as public so they can be used in other modules.
The pub keyword makes things public.
The use keyword is used to shorten the module path.
We don’t need to explicitly declare 3rd party modules.

--

--

braindead

A passionate programmer, I am eager to challenge myself to do things I’ve never accomplished before and I strive to learn and improve on my skills every day