"mod" statement working in main.rs, but not anywhere else without extra path

Coming from primarily C world, i still struggle a bit with what is the idiomatic way to separate, and "include"/use code in various parts of program.

Thanks to extensive and very helpful response i got in another thread in the past, i am sort of able to "include" code when/as needed.

However, it's still unclear to me e.g. what is the recommended way to share some code that is rather frequently used in many various application files.

The Rust book examples to me feel rather insufficient, showing how to write several mods into single file main.rs. This IMHO misses the primary point of separating the code into smaller parts, that it think of as files, not sections of same file (coming from C waters).

/src/
     main.rs
     myutils.rs
     some_ui.rs
     other_ui.rs
     ...

myutils.rs may e.g. contain 10 constants specific to generic UI design, and making it a standalone "lib" feels like overkill, otherwise the correct approach would be rather clear...

Trying something tiny bit more complex in real world, i immediately fail - is there a specific reason why mod myutils; statement works only in main.rs, but not in any other file that is sibling of main.rs.

It looks like i have to either:

  • drag all the minor stuff through the main.rs file and mod statement there, to allow crate:: prefix to work in other files for mod statement/inclusion

  • use two/three lines per each "file" include (#[path], mod, use) i want to use in my files (some_ui, other_ui, etc_ui....); this feels very verbose (compared to single line of C's #include "./myconsts.h"
    (of course, the difference in raw C include vs namespacing etc. yet, still, it feels lik, the verbosity, with, possibly, some magic... :slight_smile: )

What is the recommended pattern for similar code shared in multiple parts of the codebase?

The second option will duplicate the code multiple times. The first option is what you should do. The reason mod myutils; doesn't work in other files tha main.rs is that modules form a tree. When you write mod myutils; inside main.rs it will look for myutils.rs or myutils/mod.rs. In either case of you use mod foo; inside myutils it will look for either myutils/foo.rs or myutils/foo/mod.rs.

It's this one. This is the one you should do.

If you don't want to put myutils directly inside the crate root (main.rs or lib.rs), because myutils contains stuff that's only relevant to some_ui and other_ui, then consider scoping those together under another module:

src/
├── main.rs
└── ui
    ├── other.rs
    ├── some.rs
    └── utils.rs

Then in main.rs you might have

pub mod ui {
    pub mod other;
    pub mod some;
    mod utils; // because it's not `pub` it won't be visible outside of `ui`
}

and in other.rs and some.rs you can use the utils module from their shared parent:

use super::utils;
1 Like

yes, the foo.rs / foo/mod.rs concepts are clear and nicely described in several places, but not the explicit reason why the flat neighbors of main.rs are rather inferior in this respect of "including" other modules, at least to me being used to perceive the file hierarchy to map "directly" to package/module hierarchy of code...

First approach seems to unnecessarily pollute the main.rs with implementation details that only some of the nested files would require. But i understand that this information has to be kept someplace. I just found it a bit (maybe bit more - "dude, really" level of) strange due to other implicit / inferred behavior of the module system...

yes, by using main.rs, not lib.rs in my example, i was implying that the "utils" are private use only and need not to be handed as public sub-module.

There could be many more similarly shared files, that i so far defy calling (sub)modules (module being something bit larger/more complex in my C-head for now)

Sub-scoping seems to be the pattern i need to use, and that would be same in ultra-clean-code C world, but is not enforced as heavily as in Rust due to C's lot more permissive #includes... :slight_smile:

thanks both @bjorn3 and @trentj for your opinions/help in clarifying what i need to do.

main.rs is special because it is the "crate root", i.e. it's the only file whose path is effectively just crate. (If you're building a binary, that is; if you're building a library, the crate root is lib.rs instead.) Writing mod foo inside main.rs creates a module with the path crate::foo and writing mod bar inside that module creates a module with the path crate::foo::bar, but main.rs itself doesn't have a path; it just is the crate.

That's why main.rs is different than other .rs files in the same directory; it's a sibling to them in the filesystem, but a parent to them in the module hierarchy.

It's amazing how confusing this can be.

The break through for me was to realize that one never needs to to anything like the C "#include" or Python "import" anywhere.

For example, any crate pulled in by Cargo.toml is available for use in any file in your project.

But what one does need to do is create a module, somewhere, from a file full of functions and structs and stuff that is in your project.

And where is that "somewhere"? Why in your main.rs of course.

Which leads to the somewhat confusing situation that if you want to call "my_func()" which is in the file "my_file.rs" from "my_other_file.rs" one needs to have "mod my_file;" in main.rs. Not in "my_other_file.rs"

Someone correct me if I am wrong, but I believe one can also do the job of creating a module from a source file by putting the "mod my_file;" into lib.rs instead of main.

Then we have the confusion of "use" clauses to think about.

Turns out they don't actually include or import anything. They just allow for giving shorter names for thing that are already available.

So likely in "my_other_file.rs" one will have "use crate::my_file;"

Someone correct me if I'm wrong here. This is my mental model of the situation so far, which works around here even if it's not the best thing.

anyone, when i start mentioning/comparing to C too much, feel free to slap me... :smiley:

Yes, it feels like putting things that belong to Makefile (Cargo.toml), into main.c (main.rs/lib.rs) instead...
(yes, i know that that can be done too, thanks to a response i linked in my OP :laughing: )

imho is quite important / nicely clarifying statement that helps me as a C-tainted Rust beginner a lot.

Also great helper statement that might not be explicitly obvious (wasn't to me)!

No slaps from me. Coming from the world of C/C++ and other languages that have the "#include" style.

No slaps from those coming from the world of Python and others that have the "import"/"require" style, they get confused with Rust modules as well.

It's amazing how many questions about this have popped up here over the year I have been using Rust. It confuses everyone.

That is not to say it's bad. Just different in ways no one can guess.

Just to make life interesting, C++ is getting "modules" now. In yet another different way!

I mean, to be fair, main.rs is not that special. Whenever you have a foo/mod.rs, it has the same role where any mod statements in it apply to foo/other_file.rs rather than foo/mod/other_file.rs. It's just that in the src/ directory, the root file is called main.rs (or lib.rs) rather than mod.rs.

2 Likes

Indeed! And something that can help "clarify" the view is to try and use mod.rs files everywhere. It's unpractical, but at least the semantics are clearer:

src/
├── mod.rs # root of the crate; actually `main.rs` or `lib.rs`
├── other_file/
│   └── mod.rs
└── some_file/
    └── mod.rs

With this filesystem hierarchy, it should be easier to see why having other_file/mod.rs contain a mod some_file; will not work: there is no some_file/ directory (or the some_file.rs shortcut) inside other_file/!

But if you had:

src/
├── mod.rs
└── other_file/
    ├── mod.rs
    └── some_file/
        └── mod.rs

then having mod some_file; be located inside other_file/mod.rs would be the correct thing to do.


Now, apply the "dir_name/mod.rs can be written as dir_name.rs" shortcut and you do end up with:

src/
├── mod.rs  # main.rs / lib.rs
└── other_file.rs
└── some_file.rs

matching the first case, and

src/
├── mod.rs  # main.rs / lib.rs
└── other_file.rs
└── other_file/
    └── some_file.rs

matching the second one.