Skip to content

Latest commit

Β 

History

History
494 lines (401 loc) Β· 12.1 KB

File metadata and controls

494 lines (401 loc) Β· 12.1 KB
title Modules
slug modules

Rust Module Tree

  • The Rust module tree is the hierarchical representation of all modules in a crate.
  • It defines the namespace structure and governs how items are referenced via paths at compile time.
  • Unlike some languages like Go and JavaScript, Rust's module tree is not purely based on the file system.
    • The root of the module tree begins with either main.rs (in a binary crate) or lib.rs (in a library crate).

      [!tip] When a Rust package contains both main.rs and lib.rs, Cargo builds two separate crates with isolated module trees: a library crate (lib.rs) and a binary crate (main.rs).

      • In an optimized incremental build, lib.rs is compiled first.
      • Then, main.rs links to it as an external dependency.
    • A file or folder becomes part of the module tree only if it is explicitly declared with the mod keyword in its parent module.

    • A module can be defined in one of two ways:

      1. File module β†’ mod foo; (resolved to foo.rs or foo/mod.rs)
      2. Inline module β†’ mod foo { ... } (defined directly inside a file)
my_app
β”œβ”€β”€ Cargo.toml        (package name my_app)
└── src
    β”œβ”€β”€ main.rs       (binary crate)
    β”œβ”€β”€ lib.rs        (library crate)
    β”œβ”€β”€ foo.rs        (module foo, declared in lib.rs with `pub mod foo;`)
    β”‚                 (may contain inline modules via `mod x { ... }`)
    β”‚
    β”œβ”€β”€ bar
    β”‚   β”œβ”€β”€ mod.rs    (module bar, declared in lib.rs with `pub mod bar;`)
    β”‚   └── file.rs   (submodule of bar, declared in bar/mod.rs with `mod file;`)
    β”‚
    β”œβ”€β”€ buzz.rs       (module buzz, declared in lib.rs with `pub mod buzz;`)
    └── buzz
        └── file.rs   (submodule of buzz, declared in buzz.rs with `mod file;`)

Assume,

  • main.rs depends on std, rand(3rd party crate) and library crate.
  • lib.rs contains pub mod foo;, pub mod bar; and pub mod buzz;.
  • foo.rs, bar/file.rs, buzz/file.rs contains pub fn hello_world()
  • bar/mod.rs and buzz.rs contains mod file; and pub use file::hello_world;

The module trees are,

lib.rs (library crate)          (compiled as crate `my_app`)
  crate::
    β”œβ”€β”€ foo
    β”œβ”€β”€ bar
    └── buzz

main.rs (binary crate)          (depends on a compiled crate `my_app`)
  crate::
    (binary crate root)

  extern prelude:
    β”œβ”€β”€ std
    └── rand

  available crates:
    β”œβ”€β”€ my_app   ← library crate from lib.rs

Cargo automatically passes each dependency listed in Cargo.toml to the Rust compiler (rustc) using the --extern flag. The compiler then makes these crates available through the extern prelude. This was introduced in Rust 2018 to make external crates automatically available in every module without requiring extern crate declarations or explicit imports at the crate root.

Visibility

Default Visibility

Tip

  • Private by default to outer levels.
  • Accessible to same/child levels.
  • Use pub keyword to exposes to outer levels.
  • Everything inside a module is private by default to outer modules.
  • However, all items are accessible from within the same module or from the nested modules.
  • The pub keyword is used to make a module or its items public to outer modules.

To access elements at different levels, use the :: path operator with these patterns:

  • Current module: direct item name or self::
  • Parent module: super::
  • Crate root: crate::
  • Cross-Crate: my_app:: (the package name), Ex. accessing the library crate from the binary crate.
  • External crates in Cargo.toml: The full path with crate name (uuid::Uuid::new_v4()) or a use declaration (use uuid::Uuid) to import the elements to the current level first.

Visibility Modifiers

Tip

  • The pub keyword makes an item fully public (exposing to all outer levels).
  • Visibility modifiers restrict access to specific scopes.
Modifier Visible To
pub(self) Current module
pub(super) Parent module
pub(crate) Entire crate
pub(in path) Specific ancestor module

Inline Modules

  • Defines with the mod keyword.
  • Rust supports nested modules.

Simple

fn main() {
    inline::greet();
}

mod inline { // πŸ’‘ No pub keyword but can be accessible from the same level/ main.rs
    pub fn greet() { // πŸ’‘ function has to be public to access from outside
        println!("Hello, world!");
    }
}

Nested

fn main() {
    inline::nested::greet();
}

mod inline {
    pub mod nested { // πŸ’‘ Use `pub` keyword to access from outside
        pub fn greet() {
            println!("Hello, world!");
        }
    }
}

Visibility

Default Visibility

fn main() {
    inline::greet();
    inline::nested::greet();

    inline::nested::self_greet();
    inline::nested::super_greet();
    inline::nested::super_greet_private();

    inline::nested_private_greet();

    inline2::inline_greet();
}

mod inline {
    pub fn greet() {
        println!("Inline");
    }

    fn greet_private() { // ⭐️ public mod: private fn; can call from same level or child mod level
        println!("Inline πŸ”’");
    }

    // ⭐️ pub mod: pub fn; can call directly from outside
    pub mod nested {
        pub fn greet() {
            println!("Nested");
        }

        pub fn self_greet() {
            self::greet(); // πŸ’‘ or greet() ; call inline::nested::greet
        }

        pub fn super_greet() {
            super::greet(); // πŸ’‘ call inline::greet
        }

        pub fn super_greet_private() {
            super::greet_private();
        }
    }

    // ⭐️ private mod: pub fn ; can call from parent level
    mod nested_private {
        pub fn greet() {
            println!("Nested πŸ”‘mod");
        }
    }

    pub fn nested_private_greet() {
        nested_private::greet();
    }
}

mod inline2 {
    use crate::inline; // πŸ’‘ or `use super::inline;`  reexport inline into the module.

    pub fn inline_greet() {
        inline::greet(); // πŸ’‘ if not reexport, crate::inline::greet(); or super::inline::greet();
    }
}

Visibility Modifiers

fn main() {
    inline::nested_pub_super();
    inline::nested_pub_crate();
    inline::nested_pub_in_path();

    inline::nested::exposed_pub_self();
    inline::nested::exposed_parent_pub_self();
    inline::nested::pub_crate();
}

mod inline {
    pub mod nested {
        // Accessible only within current module or sub modules
        pub(self) fn pub_self() {
            println!("Nested self");
        }

        // Accessible only from inside or super/ 1st parent
        pub(super) fn pub_super() {
            println!("Nested super");
        }

        // Accessible anywhere within the current crate
        pub(crate) fn pub_crate() {
            println!("Nested crate");
        }

        // Accessible only within `crate::inline` and its submodules
        pub(in crate::inline) fn pub_in_path() {
            println!("Nested in-path");
        }

        // Exposed pub(self), but Clippy triggers a warning "unnecessary `pub(self)`"
        pub fn exposed_pub_self() {
            pub_self()
        }

        // Exposed parent's pub(self), but Clippy triggers a warning "unnecessary `pub(self)`"
        pub fn exposed_parent_pub_self() {
            super::pub_self()
        }
    }

    pub(self) fn pub_self() {
        println!("Inline Self");
    }

    pub fn nested_pub_super() {
        nested::pub_super()
    }

    pub fn nested_pub_crate() {
        nested::pub_crate()
    }

    pub fn nested_pub_in_path() {
        nested::pub_in_path()
    }
}

Test

When writing tests it’s a good practice to write tests inside a test module because they compile only when running tests.

#![allow(unused)]

fn greet() -> String {
    "Hello, world!".to_string()
}

#[cfg(test)] // Only compiles when running tests
mod tests {
    use super::greet; // Import root greet function

    #[test]
    fn test_greet() {
        assert_eq!("Hello, world!", greet());
    }
}

Files in the Same Directory

β”œβ”€β”€ Cargo.toml    (package name my_app)
└── src
    β”œβ”€β”€ main.rs
    β”œβ”€β”€ lib.rs
    └── foo.rs

{{< tabs count=3 group="mod-files-in-same-dir" label="Select File" >}} {{< tab label="main.rs" >}}

mod foo;

fn main() {
    my_app::greet();
    my_app::inline::greet();

    foo::greet();
    foo::inline::greet();
}

{{< /tab >}} {{< tab label="lib.rs" >}}

pub fn greet() {
    println!("Lib");
}

pub mod inline {
    pub fn greet() {
        println!("Lib:Inline");
    }
}

{{< /tab >}} {{< tab label="foo.rs" >}}

pub fn greet() {
    println!("Foo");
}

pub mod inline {
    pub fn greet() {
        println!("Foo:Inline");
    }
}

{{< /tab >}} {{< /tabs >}}

Tip

  • Accessing the library crate from the binary crate (Cross-Crate),
    • Use the crate name my_app:: path prefix to access public items defined in lib.rs.
  • Local files like foo.rs,
    • First, must be added to the module tree using mod foo; in main.rs (or lib.rs).
    • Then access its public items via foo:: path prefix.

If we load foo.rs via lib.rs, we must add pub mod foo; to lib.rs.

{{< tabs count=3 group="mod-files-in-same-dir" label="Select File" >}} {{< tab label="main.rs" >}}

fn main() {
    my_app::greet();
    my_app::inline::greet();

    my_app::foo::greet();
    my_app::foo::inline::greet();
}

{{< /tab >}} {{< tab label="lib.rs" >}}

pub mod foo;

pub fn greet() {
    println!("Lib");
}

pub mod inline {
    pub fn greet() {
        println!("Lib:Inline");
    }
}

{{< /tab >}} {{< tab label="foo.rs" >}}

pub fn greet() {
    println!("Foo");
}

pub mod inline {
    pub fn greet() {
        println!("Foo:Inline");
    }
}

{{< /tab >}} {{< /tabs >}}

Files in Different Directory

With mod.rs

Before Rust 2018:

  • Directory-based modules were conventionally defined using a mod.rs file.
  • Submodules of the directory were declared within mod.rs using mod (or pub mod) statements.
  • Ex. bar/mod.rs acts as the entry point to the bar directory module.
my_app
β”œβ”€β”€ Cargo.toml
└── src
    β”œβ”€β”€ main.rs
    └── bar
        β”œβ”€β”€ mod.rs
        └── qux.rs

{{< tabs count=3 label="Select File" >}} {{< tab label="main.rs" >}}

mod bar;

fn main() {
    bar::qux::greet();
    bar::qux::inline::greet();
}

{{< /tab >}} {{< tab label="bar/mod.rs" >}}

pub mod qux;

{{< /tab >}} {{< tab label="bar/qux.rs" >}}

pub fn greet() {
    println!("Bar:Qux");
}

pub mod inline {
    pub fn greet() {
        println!("Bar:Qux:Inline");
    }
}

{{< /tab >}} {{< /tabs >}}

Without mod.rs

Since Rust 2018:

  • Rust added an alternative approach.
  • Instead of a mod.rs file, we can use a .rs file with the same name as the directory, placed outside that directory.
  • Submodules of the directory can be declared within that .rs file using mod (or pub mod) statements.
  • Ex. buzz.rs acts as the entry point to the buzz directory module.
my_app
β”œβ”€β”€ Cargo.toml
└── src
    β”œβ”€β”€ main.rs
    β”œβ”€β”€ buzz.rs
    └── buzz
        └── qux.rs

{{< tabs count=3 label="Select File" >}} {{< tab label="main.rs" >}}

mod buzz;

fn main() {
    buzz::qux::greet();
    buzz::qux::inline::greet();
}

{{< /tab >}} {{< tab label="buzz.rs" >}}

pub mod qux;

{{< /tab >}} {{< tab label="buzz/qux.rs" >}}

pub fn greet() {
    println!("Buzz:Qux");
}

pub mod inline {
    pub fn greet() {
        println!("Buzz:Qux:Inline");
    }
}

{{< /tab >}} {{< /tabs >}}