Dolphin
Dolphin

Reputation: 38691

Is it possible to avoid using `mod.rs` files?

I read the mod document and found that I must add mod.rs in each folder, for example I have to define mod like this in my project structure:

src/
  models/
    login.rs
    mod.rs
  routes/
    login_route.rs
    mod.rs
  services/
    login_service.rs
    mod.rs
  main.rs

and use it like this in main.rs:

mod models;
mod routes;
mod services;

Why do it like that? Why design like that? If the project increase, the project has many mod.rs file just for expose mod? It is a good practice? I did not understand. What is the advantage about do it like this way? If we just do it like this way:

crate::models::login;

it is so clear and easy to understand.

Upvotes: 16

Views: 11450

Answers (4)

mashiro shiina
mashiro shiina

Reputation: 39

The question seems have gone further to "Is it possible to avoid both mod.rs and <directory>.rs" or "if neither exist but ./<directory> (or ./<directory>/<file>) exists, why not then treat mod name; as the same as if we had an empty ./<directory>/mod.rs." as @allidoiswin has mentioned.

Such a file was probably unavoidable. This could be because a directory only indicates the presence of a node in the module tree and does not describe the node. However, mod in a .rs file can fully describe the information of the node(e.g. functions in the mod, the visibility of the submodule).

Example

Suppose we want to move the mod house in main.rs to a directory.

.
└── main.rs
// main.rs
mod house {
    mod guest {}
    pub mod host { 
        pub fn clean() {
            super::clean_house();
        }
    }

    fn clean_house() {
        println!("Cleaned!");
    }
}

So we make the directory this way and want to avoid house.rs.

.
├── house
│   ├── guest.rs
│   └── host.rs
└── main.rs
// main.rs
mod house;
fn main() {
    house::host::clean();
}

// host.rs
pub fn clean() {
    super::clean_house();
}

But we found no where to write the the clean_house() function and give visibility to mod host, because house is a directory rather than a rust code file.

And the solution is to add a house.rs file, which provides the extra information of the directory house.

.
├── house
│   ├── guest.rs
│   └── host.rs
├── house.rs
└── main.rs
// house.rs
mod guest;
pub mod host;

fn clean_house() {
    println!("Cleaned!");
}

If we consider that house and house.rs are co-active, and that the house directory is where submodules of house.rs are stored, then there may be some consistency.

Upvotes: -1

diviquery
diviquery

Reputation: 759

Add a Path attribute

It is exactly like you mention in your question, but with a small annotation. This example should show how to do that:

Example

Using the same directory structure as in the OP question:

src/
  models/
    login.rs
  routes/
    login_route.rs
  services/
    login_service.rs
  main.rs

The main.rs file can declare the modules as follows:

#[path = "models/login.rs"]
mod models;

#[path = "routes/login_route.rs"]
mod routes;

#[path = "services/login_service.rs"]
mod services;

The Pros of this approach is that your mod <module_name> need not be the same as the original file or directory -- allowing for shorter names when needed.

The Con, however, is that this won't be able to refer to multiple modules in the subdirectory. (For example, if the models/ directory had more modules inside of it, it becomes a problem.)

Upvotes: 2

kmdreko
kmdreko

Reputation: 60051

Some of this is explained in What is the purpose of a mod.rs file?

Essentially Rust does not make any assumptions about the file structure and won't consider other .rs files without the developer declaring them. Instead it is intended that they use the module system to build up an organizational structure within their code.

While this differs from some other languages, this gives more control to the developer. Since import paths are decoupled the file structure, your modules, structs, functions, etc. can be re-organized and re-exported as necessary. This also allows you to declare exactly what is compiled, which can aid in conditional compilation.

So when does Rust use the file structure? When you declare a module without a body:

mod models;

It will look for a file to use for that module.

  • from a normal file, like utils.rs for example, it will look for a nested file/directory:

    ./utils/models.rs
    ./utils/models/mod.rs
    
  • from mod.rs or top-level lib.rs or main.rs (they are special in this regard), it will look for a sibling file/directory:

    ./models.rs
    ./models/mod.rs
    

The use of mod.rs conceptually allows you to use a directory as if it were a file (it is similar to index.js if you're familiar with Javascript).


Is there a way to avoid all these mod.rs files?

There are two decent alternatives:

  1. Use models.rs instead of models/mod.rs

    Simply move your mod.rs files up one level and rename them to match their directory. You do not have to modify contents of main.rs or any file. Your structure could look like this:

    src/
      models/
        login.rs
      routes/
        login_route.rs
      services/
        login_service.rs
      main.rs
      models.rs
      routes.rs
      services.rs
    

    This is slowly becoming the preferred method[citation needed] since it gives the files more descriptive names.

  2. Simply declare your module structure within main.rs:

    This is a bit unconventional, but nested module declarations will find nested files. You don't need mod.rs anywhere:

    src/
      models/
        login.rs
      routes/
        login_route.rs
      services/
        login_service.rs
      main.rs
    

    If you declare your modules in main.rs like so:

    mod models {
        mod login; // this will use ./models/login.rs
    }
    mod routes {
        mod login_route; // this will use ./routes/login_route.rs
    }
    mod services {
        mod login_service; // this will use ./services/login_service.rs
    }
    

    This isn't particularly recommended. It may work fine in small projects, but will become unwieldy as your codebase gets larger. You'll probably want to reach for mod.rs or the method shown above as a way keep your code composed.


Lastly, these transitive modules are not just for declaring other files. They're a convenient place to:

  • put shared code used within its sub-modules
  • include documentation for how its contained structs, traits, etc. are designed and/or meant to be used
  • re-export deeply nested items so they are more accessible to the rest of your code

Overall, the module system is just another level of abstraction to keep your code well-encapsulated.

Upvotes: 24

Svetlin Zarev
Svetlin Zarev

Reputation: 15683

Since Rust 2018 edition, the mod.rs file is optional:

In Rust 2015, if you have a sub-module:

// This `mod` declaration looks for the `foo` module in
// `foo.rs` or `foo/mod.rs`.
mod foo;

It can live in foo.rs or foo/mod.rs. If it has sub-modules of its own, it must be foo/mod.rs. So a bar sub-module of foo would live at foo/bar.rs.

In Rust 2018 the restriction that a module with sub-modules must be named mod.rs is lifted. foo.rs can just be foo.rs, and the sub-module is still foo/bar.rs. This eliminates the special name, and if you have a bunch of files open in your editor, you can clearly see their names, instead of having a bunch of tabs named mod.rs.

Other resources:

Upvotes: 6

Related Questions