Before you go, check out these stories!

0
Hackernoon logoIncluding Files and Deeply Directories in Rust by@josejaviasilis

Including Files and Deeply Directories in Rust

Author profile picture

@josejaviasilisJose Asilis

Entrepreneur, tinkerer, visionary, social media @josejaviasilis

One of the things that's been criticized by newbies in Rust is the file include mechanism. About 2 days ago, I spent around 5 hours on how I was supposed to include a file that was referenced deep down in a directory tree. The docs didn't help, as they were simple structures. Here I'll 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'm going to refer to traits, modules, enums, functions, structs as "resources" in general.

I'd also like to share with you a video from TensorProgramming "Rust 2018 - Modules, External Crates, Submodules, Paths and Visibility Modifier"s by TensorProgramming - https://www.youtube.com/watch?v=U8uhW9bFm-8 as this was the resource that taught me how to properly include the resources.

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

There's no such thing as a "file" when you reference your resource.

Rust doesn't see files as files, but it sees them as modules and files inside folders as sub-modules. Therefore you can't just reference them directly with a simple import or drilled namespacing a la JavaScript or C# and use them right away.

You need to create a tree of

pub mod file_name
(Called barrelling in the JS world) that expose these modules to the outer ones and (See the bullet points down below) allow them to be discovered and consumed.

How to include a module (file)

Let's suppose we have the following structure (You can grab this from the repo).

And you'd like to include several functions from modules scattered inside the

/src/house
directory into our
main.rs
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 next to the main.rs file you just specify
    mod 
    and the name of the file (without the .rs extension). E.g: mod
    welcome_home
    . In this case welcome_home does not match the name of the file (
    welcome-home.rs
    - Avoid using hyphens in naming everything; this was to showcase it's possible not that it should be done), therefore we can help Rust's compiler to identify it by providing a #path directive. Then we can reference in our code
    welcome_home::run() 
    to call the run function directly.
  • To use a mod that exists inside the file, you can just type its name as namespace and drill down to the resource you're looking for. Understand that if the module is not public you won't be able to access it within your file. Check Rust's visibility and privacy reference for more information. If you'd like to include the
    arrive_lobby
    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 found in
    house::bathroom::sink::wash_face
    first create a house.rs file at the root level directory or a mod.rs file in /src/house/mod.rs. Inside it specify the names of the folders that are included inside /src/house:
    pub mod bathroom
    . Create a
    bathroom.rs
    file in
    /src/house/bathroom.rs
    and inside it specify the modules (file names without the .rs extension) inside
    /src/house/bathroom
    :
    pub mod sink; pub mod shower; pub mod toilet; 
    Note the
    pub
    modifier as this is what gives visibility outside of the speicified file. Then include them in the code via
    use house::bathroom::sink::wash_face
    .
  • After
    use
    , if you type
    crate
    , you're telling the compiler to start looking from the root directory.
  • If you reference a resource via a mod and that resource is deeply nested, you only specify the first level of the module, and then drill it down. E.g: In case of
    house::bathroom::shower::take_shower(house::bathroom::shower::ShowerTemperature::Cold)
    function and enum, I only specified
    mod house
    , and then drilled down the namespaces with the double colon (::) until I reach it.

Including files or modules from another folder, directory or submodule

This is maybe the trickiest and most unnatural way for all new people to Rust to reference modules in other files. The

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

Rust follows a convention: You need to create a file with the name of the directory you'd like to access, and place it next to the directory you'd like to access.

You would then need to expose each of the files inside the directory that you'd like the compiler to make it accessible:

Example:

I'd like to access the contents of

shower.rs
,
sink.rs
, and
toilet.rs
inside the
/src/house/bathroom
folder.

First, I'd have to create a

/src/house/bathroom.rs
file that would make visible or barrel
shower.rs,
sink.rs 
and
toilet.rs
files. Inside it I'd 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'd need to expose this

bathroom.rs
file from the
/src/house
directory. For that, I'd need to create a
/src/house.rs
file, that 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
file visible. Note the convention:
pub mod file_name_without_rs_extension

You need to do this everytime you'd like to make a module (file) visible to the outer module (directory/folder).

Note: You'd see in the repo, and in the picture above, that there are

mod.rs
files; In versions before Rust 2018, the only way for Rust to discover these directories was for them to specify
mod.rs
inside the directory.

This became problematic because if you wanted to specify a file with the same name of the directory, the compiler would get confused. That's why starting Rust 2018 (Look for the line which says

edition = "2018"
in your
cargo.toml
file).

In this example

/src/house/mod.rs
is the same as
/src/house.rs
You can create any of those files and it would work.

Including other sibling modules (files) within submodules

To include sibling files within submodules, such as the /src/house/

main_dish/lasagna.rs
within the
diner/mod.rs
file you can get to lasagna.rs by 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 relatively to the current module's location, and then drill it to the

main_dish::lasagna
.

You can also use

super
and that will start from the parent module.

Some things you should know:

  • Prefer underscore (snake case) over hyphens. The latter tend to create problems (1) (2).
  • For exposing rust modules (files) inside a directory (folder) you need to create a folder with the directory name and export in the form of
     pub mod file_name_inside_folder
    to allow the compiler to use the file.

References

  • Rust 2018 - Modules, External 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 hyphen vs underscore 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

Tags

The Noonification banner

Subscribe to get your daily round-up of top tech stories!