Automatic modules (no more mod.rs!)

Here's a simple feature I feel should be added in Rust.

Having worked with a bunch of programming language, I find working with folders in Rust kinda annoying.

If you want to separate your code using folders, you need a mod.rs in every folder, or a file outside the folder with the folder's name. Both of these look a bit messy, but I prefer the second one.

Wouldn't it be a better idea if folders by default could count as modules? Passing trough any public function/method/etc from other modules that are inside the folder, and acting as a way to more efficiently bundle up other modules/files. Folders inside your source directory by default do nothing so I doubt it'd be a breaking change.

Additionally, there could be something like a .mod file letting you exclude/configure modules. I find that still better than a mod.rs as it's easier to read for me and it prevents me from visually confusing it with my usual source files (IDEs having different icons for different types of files helps with this, as well as the dot name similar to .gitignore).

If this is too out there, maybe an empty .mod file inside a directory could automatically trigger this behaviour instead of it being the default?

3 Likes

I don’t quite get this argument. mod.rs files are ordinary source files; what distinguishes them for your use-cases?

Also, as far as I’m aware, it’s generally quite common to put code into mod.rs files, not just the list of mod foo; declarations for nested modules.

6 Likes

This was discussed as an option when the 2018 path changes were done. Requiring explicitly mounting files with mod statements was considered too useful to remove, as

  • you can conditionally mount files with #[cfg] mod m;; and
  • you need a way to have pub items in non-pub module paths:
    • so that you don't need to pub(crate) items you want to not export,
    • so that the priv in pub pattern can work, and
    • so that you can define exported items at a different path from where they're actually exported via pub use (so you can break up big modules into multiple files as an implementation detail).

Also, you aren't actually required to have a folder.rs or folder/mod.rs: you can write mod folder { mod item; } directly in the parent module and it'll mount the child modules from the folder. (And that's before even considering the fun that is #[path] mod.)

Plus, your IDE can already use a different icon for mod.rs. The change to allow mod m; items in non-mod.rs was made supposedly to simplify the module handling, but imho it actually made it more complex by default and was thus a minor mistake.

19 Likes

For me I just find mod.rs to be visually noisy, blends in with my 'normal' source files. I consider mod.rs files to be alien since I always use them exclusively to group other files together.

For example, if I were to have a folder named commands where I store all the commands on a Serenity Discord bot. Or if I had a routes folder to store and organize all my different Rocket routes into different files. Having either a mod.rs inside a folder or a folder-name.rs next to a folder pollutes extra space in folders as well as readability a bit, my peripheral vision is definitely worse than other's but I definitely struggle to tell apart which is the mod.rs and which are the exported mod files.

I've had cases where I put code in mod files, but those were pretty rare. I feel like the requirement of an extra file to register a folder as a module does make Rust file management a bit worse compared to other languages.

3 Likes

Thanks for the additional context!

1 Like

I think being able to manually manage modules that way is useful, but that it is an annoyance in some cases (using folders to group different file modules), and I feel like a solution to that small problem wouldn't be that hard to come up with and implement since I've already sorta done it using proc macros. Emphasis on sorta, as all it did was make a new module and import in every module from a folder, which wouldn't be the desired behaviour here.

I'm not sure how the path attribute works fully, as VSCode is evil and does not provide any info about it. I can't seem to point it to a directory however (example: #[path="dir/"] mod some_group), which would be handy XD

It's documented e. g. here.

The path is always a path to a single file.

Can this be developed as an external crate?

Maybe something like

#[automatic_module]
mod something;

That will loop through all *.rs files in something/ and expand into an inline mod something { ... }.

2 Likes

Just to illustrate what @CAD97 meant by "you aren't actually required to have a folder.rs or folder/mod.rs", this is valid with no routes.rs or routes/mod.rs:

// routes/some_route.rs

pub fn route() {}
// lib.rs

mod routes {
    pub mod some_route;
}

fn stuff() {
    routes::some_route::route();
}

This works since mod routes; is basically shorthand for

mod routes {
    // insert contents of `./routes.rs` or `./routes/mod.rs`
}

If I was making a new Rust IDE, I'd combine the routes directory and routes.rs or routes/mod.rs into one entry in the file explorer.

3 Likes

I have actually done this before specifically to avoid an otherwise useless mod.rs.

5 Likes

Yep, absolutely. I recently suggested exactly this to JetBrains when they asked for feedback about RustRover.

6 Likes

Kind of useless in my case. It'd be useful if we were able to have a "modules" module that contains all of those definitions; like a project-wide mod.rs. But that'd require all code to be stored in an extra modules/ folder (I already tried making a mod.rs inside src/ and it thankfully didn't work because that idea of mine was scuffed xD)

Someone should make a VSCode extension that hides all mod.rs files, adding an extra clickable icon to folders to open their mod.rs.

the less JetBrains the better :wink:

1 Like

VSCode supports file nesting (explorer.fileNesting.* preferences) where you can make files show beneath other files using specified rules as if those are actually directories. I don't know if it is flexible enough to nest foo/* underneath foo.rs though. And I would assume it isn't flexible enough to work with mod.rs.

2 Likes

Tried messing with it, doesn't seem to support folders at all unfortunately

https://crates.io/crates/automod

As have I (example).

While I do currently use VSCode for Rust, I appreciate that JetBrains' model is money for product instead of VSCode's free means you are the product.

7 Likes

I didn't read this and went on making my own implementation for like 3 hours, was about to say I might immediately stop maintaining it since I thought I wasted my time, but automod doesn't appear to be useful in this scenario as it doesn't recursively scan directories.

So as requested by myself and @dlight, the project is over at https://github.com/FlooferLand/no_modrs and I might publish it on crates.io soon, but I'm not so sure about the name. I'm also not sure how I could improve it, but this is basically what I wish Rust had built-in xD

I was actually trying to go for a syntax similar to what @dlight suggested #[folder_module] mod something; but Rust doesn't allow you to have attribute macros on the exterior of modules due to a 6 year old open issue so I eventually settled for folder_module!(something); xD

The only (pretty major) problem with it is that intellisense isn't working, and I'm honestly not sure if I'll be able to use my own crate because it essentially turns my IDE into a text editor.

1 Like

I know Rust likes explicit over implicit. But I also find this annoying and noisy. Reusing the example above:

// routes/some_route.rs
pub fn route() {}

This is the 3rd possibility where you can put the content of this module:

// lib.rs
mod routes {
    pub mod some_route;
}

Still noisy, only now you're cluttering inside another file. As a compromise, I suggest leaving things as they are. But, recursively only for any (sub-)directory where neither of these files, nor a mod block is given this statement would be equivalent to the above:

mod routes;

Until we get normally exportable macros 2.0, this doesn't help for pub macros. So for now, there I would:

#[macro_use]
mod routes;
1 Like

Hey great crate, thanks!

That's a bummer! I can't find the issue, do you have a link?

I think this may be fixable in rust-analyzer's end. But if this were a language feature I suppose tooling would work better (besides having a better syntax)

.

It's not the prettiest thing in the world, but you can get macro_rules! to mount normally into the import namespace.

macro_rules! m {() => {}}
pub(crate) use m;

macro-vis or macro-pub are two crates that bundle this into an attribute macro for you (and make it a) work transparently at crate root and b) work for documented publicly exported macros in a fairly reasonable manner.) macro-vis is a bit more recent, macro-pub a bit lighter. (The latter is my crate.)