Skip to Content
RustBooksThe Rust BookCh 7. Managing Growing Projects with Packages, Crates, and Modules

Ch 7. Managing Growing Projects with Packages, Crates, and Modules

7.1 Packages and Crates

Crates

  • A crate is the smallest unit of code that the Rust compiler works with at a time.

  • Crates can be:

    • Binary Crates: Programs compiled to executables (e.g., command-line tools, servers). They must have a main function.
    • Library Crates: Code intended to be shared, with no main function. Examples include libraries like rand for generating random numbers.
  • The crate root is the source file where the Rust compiler starts compiling your crate. For binary crates, it’s typically src/main.rs. For library crates, it’s src/lib.rs.

Packages

  • A package is a bundle of one or more crates, typically including at least one crate.

  • A package contains:

    • A Cargo.toml file that defines how to build the crate(s).
    • The source code for the crate(s) inside the src directory.
  • A package can contain:

    • One library crate (src/lib.rs).
    • Multiple binary crates, each in separate files under src/bin/.

Example: Creating a New Package

$ cargo new my-project Created binary (application) `my-project` package $ ls my-project Cargo.toml src $ ls my-project/src main.rs
  • Cargo.toml: Defines the package configuration (dependencies, version, etc.).
  • src/main.rs: The crate root for the binary crate my-project.

Key Conventions in Cargo

  • If src/main.rs is present, the package contains a binary crate.
  • If src/lib.rs is present, the package contains a library crate.
  • A package can contain both src/main.rs (binary) and src/lib.rs (library) crates with the same name as the package.
  • Additional binary crates can be placed in the src/bin/ directory, where each file represents a separate binary crate.

Best Practices for Packages with Both a Binary and a Library Crate

When a package contains both a binary crate and a library crate, the typical goal is to maximize code reuse by organizing most of the logic in the library, while the binary crate acts primarily as an entry point to run the program.

  1. Separation of Concerns:

    • The binary crate (defined in src/main.rs) should focus on application-specific tasks like handling command-line arguments, configuration, and invoking the library crate’s functionality.
    • The library crate (defined in src/lib.rs) should contain the core functionality that can be reused by other projects or other parts of the same project.
  2. Library-Centric Design:

    • The module tree should be defined in src/lib.rs. All the core functionality, such as helper functions, data processing, and business logic, should reside in the library crate.
    • The binary crate (src/main.rs) should act as a thin wrapper that starts the executable and interacts with the public API of the library.
  3. Public API:

    • The library crate should expose its functionality via public items (functions, structs, enums, etc.) to be reusable both within the package and by external users.
    • The binary crate interacts with the library crate through its public API, using paths that start with the package name, similar to how external crates access it.

Benefits of This Organization

  1. Code Reuse:

    • By placing most of the functionality in the library crate (src/lib.rs), you allow other projects and even other parts of the same package to reuse the code.
    • The binary crate (src/main.rs) simply starts the application and calls into the library crate, keeping the binary code minimal.
  2. Testing and Maintenance:

    • The library crate can be tested independently of the binary crate, making it easier to write unit tests for the core logic.
    • If changes are needed in the functionality, you can update the library crate without having to modify the binary entry point, unless changes in behavior are required at the application level.
  3. API Design:

    • The binary crate is a consumer of the public API of the library crate, which helps ensure the library’s API is well-designed and user-friendly. As the package author, you also act as a user of your own API.
    • This practice encourages encapsulation and modularity, as the binary crate can only access the library crate’s public items.

7.2 Modules in Rust: Controlling Scope and Privacy

Rust’s module system allows you to organize your code into smaller, manageable parts, control the visibility (privacy) of your items, and handle code dependencies efficiently.

Cheat Sheet for Modules, Paths, and Visibility

  • Crate Root:

    • The compiler starts at the crate root file, which is src/lib.rs for library crates and src/main.rs for binary crates.
  • Declaring Modules:

    • To declare a module, use the mod keyword. For example, mod garden; in the crate root tells Rust to look for the garden module in:
      • Inline (using curly brackets after mod garden)
      • src/garden.rs
      • src/garden/mod.rs
  • Declaring Submodules:

    • In any module, you can declare submodules. For example, mod vegetables; inside src/garden.rs will look for the submodule in:
      • Inline (with curly brackets after mod vegetables)
      • src/garden/vegetables.rs
      • src/garden/vegetables/mod.rs
  • Paths to Items in Modules:

    • Once a module is part of your crate, you can access items in that module using the full path. For example, an Asparagus type in the garden::vegetables module can be accessed via crate::garden::vegetables::Asparagus.
  • Private vs. Public:

    • Private by Default: Items in a module are private to their parent module by default.
    • Making Modules Public: To make a module public, use pub mod.
    • Making Items Public: Use pub before items like functions, structs, or enums to make them accessible outside their module.
  • The use Keyword:

    • The use keyword creates shortcuts for long paths. For example, use crate::garden::vegetables::Asparagus; allows you to refer to Asparagus directly without the full path in the current scope.

Example: Organizing a Crate

backyard ├── Cargo.lock ├── Cargo.toml └── src ├── garden │ └── vegetables.rs ├── garden.rs └── main.rs

src/main.rs (Crate Root for Binary Crate)

use crate::garden::vegetables::Asparagus; pub mod garden; fn main() { let plant = Asparagus {}; println!("I'm growing {plant:?}!"); }
  • use crate::garden::vegetables::Asparagus;: Brings the Asparagus type into scope, so you can refer to it directly.
  • pub mod garden;: Declares the garden module, telling the compiler to load code from src/garden.rs.
  • let plant = Asparagus {};: Creates an instance of Asparagus, which is defined in the vegetables submodule.

src/garden.rs (Module File for garden)

pub mod vegetables;
  • pub mod vegetables;: Declares the vegetables submodule, making it public and telling the compiler to load code from src/garden/vegetables.rs.

src/garden/vegetables.rs (Submodule File for vegetables)

#[derive(Debug)] pub struct Asparagus {}
  • #[derive(Debug)]: Allows the Asparagus struct to be printed with println! using the {:?} formatter.
  • pub struct Asparagus {}: Defines a public Asparagus struct so that it can be used outside the vegetables module.

Module Rules in Action

  1. Start from the crate root: The compiler starts with src/main.rs (for a binary crate), and the pub mod garden; tells it to load the garden module from src/garden.rs.

  2. Declare modules and submodules: The garden module is declared in src/main.rs, and the vegetables submodule is declared in src/garden.rs.

  3. Use paths: Inside main.rs, we can access the Asparagus type via the full path crate::garden::vegetables::Asparagus or use the use keyword for a shortcut.

  4. Control privacy: The pub keyword makes both the garden and vegetables modules public, and the Asparagus struct is also public so it can be accessed from main.rs.

The use Keyword in Detail

The use keyword simplifies code by allowing shorter references. Instead of writing the full path every time, you can import items into scope. For example:

use crate::garden::vegetables::Asparagus; fn main() { let plant = Asparagus {}; }

Without use, you would need to write the full path each time:

fn main() { let plant = crate::garden::vegetables::Asparagus {}; }
  • Modules in Rust allow you to organize your code into logical groups, making it easier to manage and control the visibility of functions, structs, and other items.
  • By default, items within a module are private, but they can be made public using the pub keyword.

Step 1: Creating the Project

  1. Run the following command to create a new library project:

    cargo new restaurant --lib
  2. This creates a restaurant directory with a Cargo.toml file and a src/lib.rs file.

Step 2: Defining Modules

In src/lib.rs, we can group related functionalities into nested modules to reflect how a restaurant operates.

mod front_of_house { mod hosting { fn add_to_waitlist() {} fn seat_at_table() {} } mod serving { fn take_order() {} fn serve_order() {} fn take_payment() {} } }
  • mod front_of_house: This defines a module named front_of_house.
  • mod hosting and mod serving: These are submodules nested within front_of_house, each containing related functions.

Step 3: The Module Tree

The structure created above forms a module tree

crate └── front_of_house ├── hosting ├── add_to_waitlist └── seat_at_table └── serving ├── take_order ├── serve_order └── take_payment
  • Modules and nesting: hosting and serving are sibling modules, both contained within the parent module front_of_house.

Key Concepts

  • Module Definition: Use the mod keyword followed by the module name:

    mod front_of_house { // nested modules and items }
  • Nested Modules: You can nest modules within other modules. Each module can contain functions, structs, enums, traits, and other items.

    mod hosting { fn add_to_waitlist() {} }
  • Private by Default: Functions and items inside modules are private unless explicitly made public with the pub keyword.

Crate and Module Tree

Rust’s module system is like a filesystem directory tree, where:

  • The crate root (src/lib.rs or src/main.rs) forms the root of the module tree.
  • Nested modules are like subdirectories.
  • The crate itself forms an implicit module that acts as the root for all other modules.

7.3 Paths for Referring to an Item in the Module Tree

  • In Rust, paths are used to refer to items like functions, structs, and modules within the module tree. There are two types of paths:
  1. Absolute paths: Start from the root of the crate or an external crate.
  2. Relative paths: Start from the current module using self, super, or identifiers within the current module.
// src/lib.rs mod front_of_house { mod hosting { fn add_to_waitlist() {} } } pub fn eat_at_restaurant() { // Absolute path crate::front_of_house::hosting::add_to_waitlist(); // Relative path front_of_house::hosting::add_to_waitlist(); }
  • Absolute Path:

    • The first call to add_to_waitlist uses the full path starting from the crate root with crate::front_of_house::hosting::add_to_waitlist().
  • Relative Path:

    • The second call uses a relative path, assuming we’re in the same module as front_of_house and hosting.

Privacy and Module Access

  • By default, everything in a module is private, meaning it’s only accessible within the module itself.

  • To make items accessible to other parts of the crate, we need to use the pub keyword to make them public.

$ cargo build Compiling restaurant v0.1.0 (file:///projects/restaurant) error[E0603]: module `hosting` is private --> src/lib.rs:9:28 | 9 | crate::front_of_house::hosting::add_to_waitlist(); | ^^^^^^^ --------------- function `add_to_waitlist` is not publicly re-exported
// src/lib.rs, fixed with pub mod front_of_house { pub mod hosting { pub fn add_to_waitlist() {} } } pub fn eat_at_restaurant() { // Absolute path crate::front_of_house::hosting::add_to_waitlist(); // Relative path front_of_house::hosting::add_to_waitlist(); }
  • pub mod hosting {}: Makes the hosting module public so it can be accessed from outside front_of_house.
  • pub fn add_to_waitlist(): Makes the add_to_waitlist function public so it can be called outside the hosting module.

Now, both absolute and relative paths will work because the items are public.

Module Tree with Privacy

crate └── front_of_house (private) └── hosting (public) └── add_to_waitlist (public)

In this tree:

  • front_of_house is private, but its child module hosting is public.
  • add_to_waitlist is a public function within the hosting module.

Absolute vs. Relative Paths

Choosing whether to use an absolute or relative path depends on your project structure:

  • Absolute Paths:

    • More stable if you plan to move code around. For example, moving the eat_at_restaurant function to another module wouldn’t require updating the absolute path.
  • Relative Paths:

    • Handy when modules are closely related and likely to stay together. If the eat_at_restaurant function and the front_of_house module are moved together, the relative path remains valid.

Exposing Paths with the pub Keyword

Marking a module as pub only makes the module itself accessible but does not automatically expose its contents. You also need to mark individual items (like functions or structs) within the module as pub to make them accessible.

mod front_of_house { pub mod hosting { pub fn add_to_waitlist() {} } } pub fn eat_at_restaurant() { // Absolute path crate::front_of_house::hosting::add_to_waitlist(); // Relative path front_of_house::hosting::add_to_waitlist(); }
  • pub mod hosting {}: This makes the hosting module accessible from outside the front_of_house module.
  • pub fn add_to_waitlist() {}: This makes the add_to_waitlist function accessible outside the hosting module.

Explanation of Privacy and Paths

  1. Absolute Path:

    • The call crate::front_of_house::hosting::add_to_waitlist() works because:
      • The front_of_house module is private, but eat_at_restaurant is in the same module as front_of_house, so it can access it.
      • The hosting module is public (pub), so it can be accessed from eat_at_restaurant.
      • The add_to_waitlist function is public (pub), so it can be called from outside the hosting module.
  2. Relative Path:

    • The call front_of_house::hosting::add_to_waitlist() works because:
      • front_of_house is defined in the same module as eat_at_restaurant, so the relative path works.
      • The hosting module and the add_to_waitlist function are both public, so they can be accessed.

Using super for Relative Paths

  • In Rust, you can use the super keyword to create relative paths that start from the parent module, just like how .. is used in filesystem paths.
  • This can be useful when you need to reference items in a parent module without starting from the crate root, making it easier to restructure your module tree if necessary.

Example: Using super for Relative Paths (Listing 7-8)

fn deliver_order() {} mod back_of_house { fn fix_incorrect_order() { cook_order(); super::deliver_order(); // Call a function from the parent module } fn cook_order() {} }
  • super::deliver_order(): This relative path starts in the parent module (crate) and looks for the deliver_order function. The use of super makes it easier to move modules around while maintaining correct references.
  • In this case, the back_of_house module references deliver_order, which resides in the parent module (the crate root).

Making Structs and Enums Public

In Rust, the pub keyword is used to make structs, enums, and their fields/variants public, but there are specific rules for how visibility works for structs and enums.

Making Structs Public

When you make a struct public using pub, its fields remain private by default. You must explicitly mark each field as public if you want it to be accessible outside the module.

Example: Public Struct with Some Private Fields

mod back_of_house { pub struct Breakfast { pub toast: String, // Public field seasonal_fruit: String, // Private field } impl Breakfast { pub fn summer(toast: &str) -> Breakfast { Breakfast { toast: String::from(toast), seasonal_fruit: String::from("peaches"), // Private field set internally } } } } pub fn eat_at_restaurant() { let mut meal = back_of_house::Breakfast::summer("Rye"); meal.toast = String::from("Wheat"); // Public field can be modified println!("I'd like {} toast, please", meal.toast); // Error: meal.seasonal_fruit is private and can't be accessed // meal.seasonal_fruit = String::from("blueberries"); }
  • Public Field (toast): The toast field is marked as pub, so it can be accessed and modified outside the back_of_house module.
  • Private Field (seasonal_fruit): The seasonal_fruit field is private, so it can only be accessed within the module or through functions within the module. It cannot be modified in eat_at_restaurant.
  • Constructor Function (summer): Since seasonal_fruit is private, the Breakfast struct provides a public constructor method (summer) to initialize instances.

Making Enums Public

When an enum is made public, all of its variants automatically become public. There’s no need to mark individual variants as public, unlike struct fields.

Example: Public Enum

mod back_of_house { pub enum Appetizer { Soup, Salad, } } pub fn eat_at_restaurant() { let order1 = back_of_house::Appetizer::Soup; let order2 = back_of_house::Appetizer::Salad; }
  • Public Enum (Appetizer): Making the Appetizer enum public automatically makes its variants (Soup and Salad) accessible outside the module.
  • Variants Public by Default: Enum variants are public when the enum is public, which differs from struct fields that are private by default unless explicitly made public.

7.4 Bringing Paths into Scope with the use Keyword

In Rust, writing out full paths to access functions or modules can be repetitive. To make code more concise, the use keyword allows you to bring a path into scope, creating a shortcut that can be used throughout that scope.

mod front_of_house { pub mod hosting { pub fn add_to_waitlist() {} } } use crate::front_of_house::hosting; pub fn eat_at_restaurant() { hosting::add_to_waitlist(); // Shortcut to hosting::add_to_waitlist }
  • use crate::front_of_house::hosting;: This brings the hosting module into scope, so we don’t need to write the full path crate::front_of_house::hosting every time we use it.
  • This is similar to creating a symbolic link in a filesystem, where you create an alias for a longer path.

Scope of use

The use keyword only applies within the scope in which it is declared. If you declare use at the crate root, it doesn’t affect submodules. Each module has its own scope, so you need to repeat the use statement in other modules if you want to use the shortcut there.

If you try to use the hosting shortcut in a different module, such as a new customer module, the code will not compile:

mod front_of_house { pub mod hosting { pub fn add_to_waitlist() {} } } use crate::front_of_house::hosting; mod customer { pub fn eat_at_restaurant() { hosting::add_to_waitlist(); // Error: hosting is not in this scope } }

The compiler will produce an error because the use crate::front_of_house::hosting; statement only applies to the scope where it’s declared (in this case, the crate root), not in the customer module:

error[E0433]: failed to resolve: use of undeclared crate or module `hosting`

To resolve this issue, you have a few options:

  1. Move the use Statement into the customer Module:

    You can place the use statement inside the customer module so that it applies within that module’s scope:

    mod front_of_house { pub mod hosting { pub fn add_to_waitlist() {} } } mod customer { use crate::front_of_house::hosting; pub fn eat_at_restaurant() { hosting::add_to_waitlist(); // Now works because hosting is in scope } }
  2. Use super to Access the Parent Module:

    If the hosting module is in the parent module, you can use super:: to access it from within the customer module:

    mod front_of_house { pub mod hosting { pub fn add_to_waitlist() {} } } mod customer { pub fn eat_at_restaurant() { super::super::front_of_house::hosting::add_to_waitlist(); // Using super to refer to parent } }

Compiler Warning for Unused Imports

  • you might also get a warning if the use statement is declared but not used within its scope:
warning: unused import: `crate::front_of_house::hosting`

This occurs because the use statement is declared in the crate root, but it’s not actually used there. This can be fixed by moving the use statement into the appropriate scope where it is needed, such as inside the customer module.

Idiomatic use Paths

Rust provides idiomatic patterns for using the use keyword to bring items into scope. This not only improves code readability but also follows conventions that are familiar to the Rust community.

mod front_of_house { pub mod hosting { pub fn add_to_waitlist() {} } } use crate::front_of_house::hosting; // Bring the module into scope pub fn eat_at_restaurant() { hosting::add_to_waitlist(); // Call the function through the module }

This is idiomatic because:

  • By bringing the module (hosting) into scope, the code still indicates that add_to_waitlist is not locally defined—it’s part of the hosting module.
  • This improves code clarity and avoids confusion about the function’s origin.

Unidiomatic Example (Listing 7-13)

use crate::front_of_house::hosting::add_to_waitlist; // Bring function directly into scope pub fn eat_at_restaurant() { add_to_waitlist(); // Less clear where the function is defined }

While this approach works, it is unidiomatic because:

  • It makes the function look like it is locally defined, hiding its module origin.
  • The code becomes less clear about the function’s source, which can lead to confusion when reading or maintaining the code.

Bringing Structs, Enums, and Other Items into Scope: Full Paths

When bringing structs or enums into scope, it is idiomatic to specify the full path

use std::collections::HashMap; // Bring the struct directly into scope fn main() { let mut map = HashMap::new(); map.insert(1, 2); }
  • This pattern is commonly used with structs like HashMap, and it is a familiar convention in Rust.
  • It provides a clear indication of the origin of the type.

Handling Naming Conflicts with the as Keyword

When two items with the same name are brought into scope, Rust will throw an error due to ambiguity. You can resolve this by using their parent modules or by using the as keyword to provide an alias.

use std::fmt; use std::io; fn function1() -> fmt::Result { // --snip-- } fn function2() -> io::Result<()> { // --snip-- }

By keeping fmt::Result and io::Result within their parent modules, we avoid conflicts between the two types that share the same name (Result).

You can also rename one of the types using as:

use std::fmt::Result; use std::io::Result as IoResult; // Alias to avoid conflict fn function1() -> Result { // --snip-- } fn function2() -> IoResult<()> { // --snip-- }
  • The as keyword creates an alias (IoResult), allowing both Result types to coexist without conflict.
  • This approach is also idiomatic and offers flexibility when working with naming conflicts.

Re-exporting Names with pub use

Sometimes, you may want to re-export items from your module to simplify external code that depends on your library. You can do this using the pub use combination, which exposes internal items as part of your public API.

mod front_of_house { pub mod hosting { pub fn add_to_waitlist() {} } } pub use crate::front_of_house::hosting; // Re-export the hosting module pub fn eat_at_restaurant() { hosting::add_to_waitlist(); }
  • pub use allows external code to refer to hosting directly via restaurant::hosting::add_to_waitlist(), instead of the full path restaurant::front_of_house::hosting::add_to_waitlist().
  • This makes your public API simpler and easier to use while allowing you to maintain a clean internal structure.

When to Use pub use

Re-exporting is useful in cases where:

  • You want to hide internal complexity or module hierarchy from external users.
  • You want to provide a cleaner, more intuitive public API that aligns with how external users will think about your code.

For example, in the context of a restaurant, internal staff might think of “front of house” and “back of house,” but customers just care about the services available (e.g., hosting). Re-exporting allows you to keep internal code organized while exposing a cleaner interface.

Using External Packages in Rust

In Rust, external packages (or “crates”) are stored and shared via crates.io , the Rust package registry. To use an external package in your project, you follow these steps:

1. Add the Dependency to Cargo.toml

First, you need to specify the external package and its version in your project’s Cargo.toml file. For example, to use the rand crate, you would add:

[dependencies] rand = "0.8.5"
  • This tells Cargo to download the specified version of the rand crate from crates.io , along with any of its dependencies.

2. Bring the External Package into Scope with use

Once the dependency is listed in Cargo.toml, you can bring it into scope in your code by using the use keyword, just like you do with standard library items.

For example, in the guessing game example from Chapter 2, we used the rand crate to generate random numbers:

use rand::Rng; fn main() { let secret_number = rand::thread_rng().gen_range(1..=100); }
  • rand::Rng: This brings the Rng trait from the rand crate into scope, enabling us to call methods like gen_range.
  • rand::thread_rng(): This function provides a random number generator that’s local to the current thread.

3. Accessing the Standard Library Crate

The standard library (std) is also an external crate, but it’s included with Rust, so you don’t need to add it to Cargo.toml. However, you still need to bring its components into scope using use, similar to how you handle external crates.

For example, to use a HashMap from the standard library, you would write:

use std::collections::HashMap; fn main() { let mut map = HashMap::new(); map.insert(1, 2); }
  • std::collections::HashMap: This imports the HashMap type from the std crate’s collections module.

Using Nested Paths to Clean Up Large use Lists

When multiple items from the same module or crate need to be brought into scope, Rust allows you to use nested paths to group these imports into a single use statement.

use std::{cmp::Ordering, io};
  • std::{cmp::Ordering, io}: This statement brings both std::cmp::Ordering and std::io into scope in a single line.
  • This approach helps reduce the vertical space taken up by multiple use statements.

Nested Paths at Any Level

You can also use nested paths at any level of a path, which is helpful when dealing with multiple imports that share a common subpath.

use std::io::{self, Write};
  • self: Refers to the current module (std::io), so this imports both std::io and std::io::Write in one line.

The Glob Operator in Rust (*)

The glob operator (*) allows you to bring all public items from a module or crate into scope in one statement, without specifying each item individually.

use std::collections::*;
  • This statement brings all public items from the std::collections module into scope, such as HashMap, VecDeque, BTreeMap, and others.
  • You don’t need to specify each item manually, which can be convenient in certain situations.

When to Use the Glob Operator

The glob operator can simplify imports, but it should be used with caution because it can make it harder to know where particular items come from, leading to less readable code. Here are the most common scenarios where it might be used:

  1. In Tests:

    • When writing tests, the glob operator is often used to bring all items from the test module into scope.
    • For example, importing everything from a tests module when you want access to all the test functions and utilities.
  2. Prelude Pattern:

    • The prelude pattern in Rust allows certain frequently-used items to be automatically brought into scope for convenience. The glob operator is used internally to make these items readily available without requiring users to explicitly import them.
    • The standard library prelude includes items like Option, Result, and Vec, which are automatically in scope in every Rust program.

Potential Downsides of Using the Glob Operator

  1. Ambiguity:

    • Using * can make it harder to know exactly which items are being brought into scope, especially in larger programs or when multiple modules are involved.
    • This can lead to confusion or naming conflicts if two items with the same name are imported from different modules.
  2. Decreased Readability:

    • When you use the glob operator, readers of the code may not immediately know where a specific item (e.g., a struct or function) is coming from, which can reduce the clarity and maintainability of the code.
  3. Naming Conflicts:

    • If multiple modules contain items with the same name, using the glob operator may result in naming conflicts that are difficult to resolve.

7.5 Separating Modules into Different Files in Rust

As your Rust project grows, you may want to organize your code by splitting modules into separate files. Rust makes this easy and idiomatic by associating modules with specific file structures.

Step 1: Moving the front_of_house Module to a Separate File

First, let’s take the front_of_house module and move it into its own file.

1.1 Modify src/lib.rs

Remove the body of the front_of_house module and replace it with a mod declaration

mod front_of_house; pub use crate::front_of_house::hosting; pub fn eat_at_restaurant() { hosting::add_to_waitlist(); }
  • The mod front_of_house; tells Rust to look for the front_of_house module definition in an external file (in this case, src/front_of_house.rs).
  • This code won’t compile until you create the src/front_of_house.rs file.

1.2 Create src/front_of_house.rs

Now, move the contents of the front_of_house module into a new file named src/front_of_house.rs:

pub mod hosting { pub fn add_to_waitlist() {} }
  • This file defines the front_of_house module and its submodule hosting.

Step 2: Moving the hosting Submodule to a Separate File

Now, let’s move the hosting module into its own file.

2.1 Modify src/front_of_house.rs

Replace the body of the hosting module with a mod declaration:

pub mod hosting;
  • This tells Rust to look for the hosting module in a file named src/front_of_house/hosting.rs.

2.2 Create src/front_of_house/hosting.rs

Now, move the contents of the hosting module into a new file named src/front_of_house/hosting.rs:

pub fn add_to_waitlist() {}
  • This file now contains the implementation of the hosting module.

How Rust Finds Module Files

  • For modules declared at the crate root (lib.rs or main.rs), Rust looks for files in either:

    • src/module_name.rs (recommended and more modern)
    • src/module_name/mod.rs (older style, still supported)
  • For submodules inside other modules (e.g., hosting inside front_of_house), Rust expects files in:

    • src/parent_module/submodule.rs (recommended)
    • src/parent_module/submodule/mod.rs (older style)

Note: Avoid mixing both styles for the same module (e.g., having both src/module.rs and src/module/mod.rs), as this will result in a compiler error.

Why Split Modules into Separate Files?

  1. Improved Readability: As your project grows, having all modules in a single file can make it difficult to navigate. Splitting code into multiple files improves readability.

  2. Logical Separation: Large modules can be broken into smaller, logical pieces, allowing developers to focus on a specific part of the code.

  3. Maintainability: With well-organized code spread across multiple files, it becomes easier to manage, debug, and extend the project.

Alternate File Paths

  • Modern Style (Recommended):

    • src/module_name.rs for top-level modules.
    • src/parent_module/submodule.rs for submodules.
  • Older Style (Still Supported):

    • src/module_name/mod.rs for top-level modules.
    • src/parent_module/submodule/mod.rs for submodules.

The modern style avoids multiple files named mod.rs, which can be confusing when multiple such files are open in your editor.

Last updated on