Include files and directories deep into Rust

Profile picture of Jose Javi Asilis HackerNoon

Jose Javi Asilis

Excellent and getting better and better 🔥🔥🔥! Focused on building tech startups to shape the world and solve big problems 🚀.

One of the things that has been criticized by newbies in Rust is the file inclusion mechanism. About 2 days ago I spent about 5 hours on how I was supposed to include a referenced file deep in a directory tree. The documents did not help, because they were simple structures. Here I will show you how you can include your code in different parts of the application, even when the structure is complex, and save you hours on how to do this supposedly trivial task.

I will refer to traits, modules, enums, functions, structs as “resources” in general.

I would also like to share with you a video of Tensor Programming “Rust 2018 – Modules, Outer Crates, Submodules, Paths and Visibility Modifiers” by TensorProgramming – https://www.youtube.com/watch?v=U8uhW9bFm-8 because this is the resource that taught me to correctly include the Resources.

An example repository can be found here: https://github.com/superjose/rust-include-files-example

There is no “file” when you reference your resource.

Rust doesn’t see files as files, but it does see them as modules, and files in folders as submodules. Therefore, you can’t just reference them directly with a simple import or drilled namespace a la JavaScript or C# and use them immediately.

You need to create a tree of

pub mod file_name

(Called barrel in the JS world) which expose these modules to externals and (See bullet points below) allow them to be discovered and consumed.

How to include a module (file)

Suppose we have the following structure (you can get it from the repository).

picture

And you would like to include several scattered module functions inside the

/src/house

directory in our

main.rs

to file:

mod house;
// Please, do not use hyphens as this is proven to have an
// inconsistent behavior.
// https://github.com/rust-lang/book/issues/1709
// https://rust-lang.github.io/rfcs/0940-hyphens-considered-harmful.html
// https://stackoverflow.com/a/57535534/1057052
#[path = "./welcome-home.rs"]
mod welcome_home;
// Includes the function directly
// When using crate, you use the root directory to include everything.
use crate::house::diner;
use house::bathroom::sink::wash_face;
use house::kitchen::prepare::food_preparation::prepare_food;

fn main() {
    let user = "Jose";
    building::lobby::arrive_lobby();
    welcome_home::run(user);
    house::bathroom::shower::take_shower(house::bathroom::shower::ShowerTemperature::Cold);
    wash_face();
    house::bathroom::toilet::use_toilet();
    prepare_food();
    diner::eat();
}

// https://doc.rust-lang.org/reference/visibility-and-privacy.html
/**
 * From the docs (Link above)
 *
 * By default, everything in Rust is private, with two exceptions:
 * Associated items in a pub Trait are public by default; Enum
 * variants in a pub enum are also public by default. When an
 * item is declared as pub, it can be thought of as being accessible
 * to the outside world.
 */
mod building {
    pub mod lobby {
        pub(in crate) fn arrive_lobby() {
            println!("You have arrived to the lobby");
        }
    }
}
  • To include a file alongside the main.rs file you just specified
    mod 

    and the file name (without the .rs extension). e.g. module

    welcome_home

    . In this case, welcome_home does not match the file name (

    welcome-home.rs

    Avoid using hyphens to name everything; it was to show that it is possible, not that it had to be done), so we can help the Rust compiler identify it by providing a #path directive. Then we can reference in our code

    welcome_home::run() 

    to directly call the run function.

  • To use a mod that exists in the file, you can simply type its name as a namespace and navigate to the resource you are looking for. Understand that if the module is not public, you will not be able to access it in your file. See the Rust Visibility and Privacy Reference for more information. If you want to include the
    arrive_lobby

    a function. Since it’s in the same file, you can just

    building::lobby::arrive_lobby

    and the module is visible.

  • You can use a resource directly by specifying it with the
    use

    keyword. This means that you don’t need to write

    house::bathroom:sink::wash_face()

    to use the function, but simply

    wash_face()

    .

  • In case of
    wash_face 

    which is in

    house::bathroom::sink::wash_face

    first create a house.rs file in the root level directory or a mod.rs file in /src/house/mod.rs. Inside, specify the names of the folders included in /src/house:

    pub mod bathroom

    . To create a

    bathroom.rs

    drop in

    /src/house/bathroom.rs

    and inside specify the modules (filenames without the .rs extension) inside

    /src/house/bathroom

    :

    pub mod sink; pub mod shower; pub mod toilet; 

    Note the

    pub

    modifier because that’s what gives visibility outside of the specified file. Then include them in the code via

    use house::bathroom::sink::wash_face

    .

  • After
    use

    , if you type

    crate

    , you tell the compiler to start looking from the root directory.

  • If you reference a resource through a mod and that resource is deeply nested, you only specify the first level of the module and then crawl it. Eg: In case of
    house::bathroom::shower::take_shower(house::bathroom::shower::ShowerTemperature::Cold)

    function and enum, I only specified

    mod house

    , then I explored the namespaces with the colons (::) until I reached it.

Include files or modules from another folder, directory or submodule

This is perhaps the trickiest and least natural way for all new Rust users to reference modules in other files. the

wash_face

The function is an example of a deeply nested function. I encourage you to view the repo to better understand how everything works.

Rust follows a convention: you must create a file with the name of the directory you want to access and place it next to the directory you want to access.

You will then need to expose each of the files inside the directory that you want the compiler to make accessible:

Example:

I want to access the content of

shower.rs

,

sink.rs

, and

toilet.rs

inside of

/src/house/bathroom

case.

picture

First I have to create a

/src/house/bathroom.rs

file that would make visible or rollover

shower.rs,
sink.rs 

and

toilet.rs

files. Inside, I would specify the name of each of the files as public modules:

// Contents of /src/house/bathroom.rs
pub mod shower;
pub mod sink;
pub mod toilet;

Then I would need to expose this

bathroom.rs

file of the

/src/house

phone book. For this I would need to create a

/src/house.rs

file, which would export the

bathroom.rs

file that I just defined now.

// Contents of /src/house/mod.rs or /src/house.rs
pub mod bathroom;
pub mod diner;
pub mod kitchen;

As you can see

pub mod bathroom

makes

bathroom.rs

visible file. Note the convention:

pub mod file_name_without_rs_extension

You must do this Everytime you want to make a module (file) visible to the external module (directory/folder).

To note: You would see in the repo, and in the image above, that there is

mod.rs

files; In versions prior to Rust 2018, the only way for Rust to discover these directories was for them to specify

mod.rs

inside the directory.

This was getting problematic because if you wanted to specify a file with the same directory name, the compiler would get confused. This is why as of Rust 2018 (look for the line that says

edition = "2018"

in your

cargo.toml

to file).

In this example

/src/house/mod.rs

is the same as

/src/house.rs

You can create any of these files and it would work.

Include other sibling modules (files) in submodules

picture

To include sibling files in submodules, such as /src/house/

main_dish/lasagna.rs

within the

diner/mod.rs

file you can access lasagna.rs using the

self 

keyword in your

use

statements.

pub mod dessert;
pub mod main_dish;
use self::main_dish::lasagna;
// use super::diner::main_dish::lasagna;

pub fn eat() {
    lasagna::eat_lasagna();
    let candy = dessert::candy::Candy::new_chocolate(10);
    dessert::candy::eat_dessert(candy);
}

For example, the code below uses self to navigate relative to the location of the current module, then explores it to the

main_dish::lasagna

.

You can also use

super

and it will start from the parent module.

Some things you need to know:

The references

  • Rust 2018 – Modules, Outer Crates, Submodules, Paths and Visibility Modifiers by TensorProgramming – (Best resource that explains how to include everything) https://www.youtube.com/watch?v=U8uhW9bFm-8
  • Including crates with a hyphen against the underlined debate: https://github.com/rust-lang/cargo/issues/2775
  • https://doc.rust-lang.org/reference/visibility-and-privacy.html
  • https://github.com/rust-lang/book/issues/1709
  • https://stackoverflow.com/questions/57535061/how-to-use-another-file-in-rust-module-with-hyphens-in-it-
  • https://users.rust-lang.org/t/how-to-call-a-function-in-another-file-but-the-same-crate/15214-
  • https://stackoverflow.com/a/26390046/1057052
  • https://stackoverflow.com/questions/45519176/how-do-i-use-or-import-a-local-rust-file
  • https://stackoverflow.com/questions/26224947/how-do-i-do-a-basic-import-include-of-a-function-from-one-module-to-another-in-r
Profile picture of Jose Javi Asilis HackerNoon
through Jose Javi Asilis @josejaviasilis.Excellent and getting better and better 🔥🔥🔥! Focused on building tech startups to shape the world and solve big problems 🚀.

Read my stories

Keywords

Comments are closed.