Pub using all in central mod.rs

I am very new to Rust and figuring out structure.
Is it a viable option to just "pub use" all my mods inside one project?
To save me from putting detailed usings in each and every file?
I regard my project as 1 'namespace' where everything is known to everything...

// main.rs
mod consts; 
mod data;
mod directions;
mod timer;
mod misc; 
mod funcs; 
mod bits;
// and so on...

//mod.rs
pub use consts::*;
pub use data::*;
pub use directions::*;
pub use timer::*;
pub use misc::*;
pub use funcs::*;
pub use bits::*;
// and so on

If it compiles... I personally believe you are leaving a lot of the potential of the module system (loose coupling, high cohesion) unused with your approach. But I personally have come to value when everything is not known by everything else, especially when the project gets bigger. I'm also not a fan of asterisk imports as with them it is not immediately visible from your source codeβ€”which is important if you are not running a language server to aid code navigationβ€”where your names are coming from.

5 Likes

Aha, so you prefer using everything explicitely and detailed in every .rs file?
I agree it is more structured that way...

Hi,

I am very new to Rust, too. This is the current project I am developing to learn the actix-web framework.

Just like in Delphi and Python, I am keeping modules (units in Delphi) in their logical directories:

.
β”œβ”€β”€ .env
β”œβ”€β”€ Cargo.toml
β”œβ”€β”€ cert
β”‚ β”œβ”€β”€ cert-pass.pem
β”‚ β”œβ”€β”€ key-pass-decrypted.pem
β”‚ └── key-pass.pem
β”œβ”€β”€ migrations
β”‚ β”œβ”€β”€ mysql
β”‚ β”‚ └── migrations
β”‚ β”‚     β”œβ”€β”€ 20231128234321_emp_email_pwd.down.sql
β”‚ β”‚     └── 20231128234321_emp_email_pwd.up.sql
β”‚ └── postgres
β”‚     └── migrations
β”‚         β”œβ”€β”€ 20231130023147_emp_email_pwd.down.sql
β”‚         └── 20231130023147_emp_email_pwd.up.sql
β”œβ”€β”€ README.md
β”œβ”€β”€ src
β”‚ β”œβ”€β”€ auth_handlers.rs
β”‚ β”œβ”€β”€ auth_middleware.rs
β”‚ β”œβ”€β”€ bh_libs
β”‚ β”‚ β”œβ”€β”€ api_status.rs
β”‚ β”‚ └── australian_date.rs
β”‚ β”œβ”€β”€ bh_libs.rs
β”‚ β”œβ”€β”€ config.rs
β”‚ β”œβ”€β”€ database.rs
β”‚ β”œβ”€β”€ handlers.rs
β”‚ β”œβ”€β”€ helper
β”‚ β”‚ β”œβ”€β”€ app_utils.rs
β”‚ β”‚ β”œβ”€β”€ constants.rs
β”‚ β”‚ β”œβ”€β”€ endpoint.rs
β”‚ β”‚ β”œβ”€β”€ jwt_utils.rs
β”‚ β”‚ └── messages.rs
β”‚ β”œβ”€β”€ helper.rs
β”‚ β”œβ”€β”€ lib.rs
β”‚ β”œβ”€β”€ main.rs
β”‚ β”œβ”€β”€ middleware.rs
β”‚ └── models.rs
β”œβ”€β”€ templates
β”‚ β”œβ”€β”€ auth
β”‚ β”‚ β”œβ”€β”€ home.html
β”‚ β”‚ └── login.html
β”‚ └── employees.html
└── tests
    β”œβ”€β”€ common.rs
    β”œβ”€β”€ test_auth_handlers.rs
    β”œβ”€β”€ test_handlers.rs
    └── test_jsonwebtoken.rs

With this layout, the content of my src/helper.rs (for example) is:

pub mod constants;
pub mod app_utils;
pub mod endpoint;
pub mod messages;
pub mod jwt_utils;

In src/lib.rs, I can do the following:

...
pub mod helper;
...
use crate::helper::app_utils::{
    make_api_status_response,
    build_authorization_cookie,
};

use crate::helper::messages::TOKEN_STR_JWT_MSG;
...

If I am not happy, I will move modules around until I feel happy again.

I am using Visual Code Studio with Rust-Analyzer installed, they help a lot flushing out modules importing problems and I also find that it is very efficient with suggesting correct importing paths, please keep in mind, that is just how I feel about these tools.

Best regards,

...behai.

2 Likes

Thanks. Your project is more complicated regarding structure.
Mine is a chess uci engine (learning Rust, also did Delphi long time ago)
and there are (max) 40 files just flat out in the 'src' folder.
(too bad i cannot name a mod "move")

Anyway... in a later stage (just finished the movegenerator) I will review the "using" structure.
The .rs will be more readable.

In general I dislike (deeply) nested directory structures if they are not 100% clear.
That is why I maybe flattened it too much with this "use all"

Often it is impossible to make a "tree" because there are always interdepencies.

Why is it an issue? Any IDE will easily add the required includes on demand, and if you're not using an IDE, you really should. It's 2024, and the tooling is really good.

I mean, you can do it. The language won't stop you. I wouldn't consider it good practice, though. You're basically foregoing the module & privacy system entirely. It's about as much code organization as dumping everything in a single file. Everything can refer to anything at any time, and understanding the interrelations is a huge pain for any medium-sized project.

The general advice is "use the smallest possible visibility scope which satisfies your requirements". When I try to understand a library, the first question I ask is "what can I use and what functionality exists", and proper visibility is critical to get that answer easily. Best-case, a library will have just a handful of public types and functions, and everything else can be ignored as implementation detail. The same is true for individual modules. The smaller its public API surface, the better.

Rust doesn't restrict you from creating interdependencies between modules in a single crate, regardless of their layout. You need to declare sufficiently wide visibility of the item and its containing module, obviously, but other than that there are no restrictions. E.g. you could make everything pub and then structure it in whatever tree you like. This means that module nesting should correspond to their logical relations, with nested submodules typically defining implementation details or special cases of their containing module.

Still, it's best to reduce interdependencies as much as possible, for your own sanity. For larger projects, debugging an issue or making changes in situations where everything depends on everything borders on infeasible. Of course, it won't matter as much if your project is small, a few KLoC. Single files can be larger in some projects.

This makes me think that you may be heavily overusing modules. Rust is not Java, you shouldn't create a separate module per type, or something like that. You're free to lay out your code on disk in whichever way you like, so do it in a way which clarifies logical relations and helps with modularization, not in a way which follows some arbitrary primitive rule, like "each struct in its own file" or "a file can be no more than 100 lines".

6 Likes

Thanks for your answer.

That is my whole struggle with rust structures / mods etc.
I create a file "xyz.rs" and the rust compiler makes a mod of it!
The separate files - by the way - are very logical and I am certainly not one of these freaks who dedicates a whole file to some empty base interface :slight_smile:
On the other hand, putting all base types in one file is inconvenient. my Square type has quite some functions for example.
(I worked a long time with C# and there you can let each file be part of a 'namespace'.)
But already - after 2 weeks - a Rust fan I believe. The compiler is insane! And the performance as well.

1 Like

Note that you can split a data type's definition into a separate module from its various impl blocks if that helps make the files more logical. See Rust Playground for an example, where I have a module types that defines a struct Foo, and two _impl modules that provide implementations on it.

This is a technique to be used with care, but if you have a type with lots of methods, it can be helpful to split it into multiple modules so that you've got easier-to-read code.

2 Likes

Yes I am aware and have some cases which are candidate,
there where I need special functionality used only in one hidden dark place.
Still getting used to it... steep learning curve Rust has.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.