A deep dive in module system of Rust 2018

Module System of Rust 2018

The blue title above is a link to github, all codes are there.

Try to cover all possibilities of module dependencies in Rust 2018 to help people like me who is confusing about Rust's module system. Hopefully all dependency directions are covered, currently including looking at the followings, if you think something missing, please let me know:

  • Parents (even looking at modules sitting at the root level)
  • Siblings (even at root level)
  • Cousins
  • Sons

Cargo run outputs:

Caller = main::main(), Callee = d1/m1::prn()
Caller = d1/m1::prn(), Callee = d1/d11/m11::prn()
Caller = d1/d11/m11::prn(), Callee = d1/m1::prn2()
Caller = d1/d11/m11::prn(), Callee = util::prn()
Caller = main::main(), Callee = d1/m2::prn()
Caller = d1/m1_1::prn(), Callee = d1/m1::prn()
Caller = d1/m1::prn(), Callee = d1/d11/m11::prn()
Caller = d1/d11/m11::prn(), Callee = d1/m1::prn2()
Caller = d1/d11/m11::prn(), Callee = util::prn()
Caller = main::main(), Callee = d1/d11/m11::prn()
Caller = d1/d11/m11::prn(), Callee = d1/m1::prn2()
Caller = d1/d11/m11::prn(), Callee = util::prn()
Caller = main::main(), Callee = d2/m2::prn()
Caller = d2/m2::prn(), Callee = d1/d11/m11::prn()
Caller = d1/d11/m11::prn(), Callee = d1/m1::prn2()
Caller = d1/d11/m11::prn(), Callee = util::prn()
Caller = main::main(), Callee = d2/d22/m22::prn()
Caller = main::main(), Callee = config::prn()
Caller = config::prn(), Callee = d2/m2::prn()
Caller = d2/m2::prn(), Callee = d1/d11/m11::prn()
Caller = d1/d11/m11::prn(), Callee = d1/m1::prn2()
Caller = d1/d11/m11::prn(), Callee = util::prn()
Caller = config::prn(), Callee = d2/d22/m22::prn()
Caller = config::prn(), Callee = util::prn()

Whatever that is, it does not help my confusion with Rust's module system.

I'd like to know what is your confusion, maybe I didn't take account of that, thank you.

I’m not quite sure what I’m looking at. Can you summarize the findings of your investigation?

I'm not totally sure what my confusion is. What with being confused and all.

I guess it started from the desire to use code in file A from file B and finding out I can't do it without putting a "mod some_mod" statement in file C. Where C is where my main() lives.

Just now, playing with example code using Yew I find that to create "component", i.e. some Rust code, I need a "components" directory with a "mod.rs" file in it. Which contains nothing but:

pub mod button;
pub mod fetcher;

Oh, and apparently I need:

mod components;

In my top level "lib.rs" file. Even if that file itself never uses any such components.

Why for goodness sake?

At some point I get this mess working. Without beginning to understand the logic of it all.

Sorry, guys, The blue title is a link to github, all codes are there, I'll modify the post.

The blue title is pointing to github.

The blue title is pointing to github, all codes are there. At the very beginning of playing around with Rust, I used mod.rs too, now I switch to Rust 2018 with no mod.rs. But I have to say Rust's module system is not really as good as other languages, like Java.

Ah ha, OK.

I would be very interested to hear how to get rid of that "mod.rs".

And how to get rid of my "components" directory. I mean really, with only a hand full of files in a project I would be happier with them all being in the src directory.

Somehow my confusion is compounded by the fact that when the compiler says it cannot find this or that it suggests creating such subdirectories and so on. When I do that it complains about something else. And around and around....

Every module must be both declared and defined (except the root module, which is defined but never declared). The mod keyword declares a module and, if you follow if with a block, also defines it:

pub mod components {
    pub struct MyComponent;
}

If you don't use a block with mod (i.e. pub mod components;) then the compiler will look for the definition of the module components in either of the files ./components.rs or ./components/mod.rs, where . is the directory of the file containing the mod statement (at least if the statement is not itself inside a mod block, which is almost always the case).

In Rust 2018 you can always replace foo/mod.rs with foo.rs; it's as simple as that.

You can always do this:

// in `src/lib.rs`
pub mod components;
// in `src/components.rs`
pub mod button {
    // button stuff goes here
}

pub mod fetcher {
    // fetcher stuff goes here
}

Or even, if you prefer, this:

// in `src/lib.rs`
pub mod components {
    pub mod button {
        // button stuff goes here
    }

    pub mod fetcher {
        // fetcher stuff goes here
    }
}

But, but, I don't want a src/components.rs that then has to contain all my button, fetcher, etc.

In my world model I want:

Files:

lib.rs
a.rs
b.rs
c.rs
...

I want to be able to use whatever is in any of those files from any other. Without needing any intermediary junk.

Maybe that is just not possible.

This is not possible unless you use #[path = ...] because of the two-level hierarchy (root contains components contains button and fetcher). button and fetcher would have to be modules directly under the root.

But it can be achieved like this (I think):

src/
|- lib.rs
|- button.rs
|- fetcher.rs
// in `src/lib.rs`
#[path = "."]
pub mod components {
    pub mod button;

    pub mod fetcher;
}

(Edit: fixed my botched solution.)

1 Like

@2371828
An attempt to summarize what I found, but I don't think I can do well, codes are more than words, I just put something here that impressed me:

  1. If you need to import modules from somewhere else in main.rs (this is not necessarily its file name, just any name as long as it is the entry point of your program) which should sit at the root level just below src folder, please use the package name in the section [package] in Carogo.toml in the module path in the use statement.

  2. Say A, B are all under src folder, you need A to import something from B, the use statement should be like 'use crate::' rather than anything else.

  3. lib.rs is needed under src, it declares all modules you need somewhere, it can be written in a nested way using curly parantheses, including the modules sitting under src folder, dont't foget 'pub'.

  4. If there is a folder D, a D.rs is needed, it is sitting right sibling to D, the content is 'pub mod D'. But, if D is just under src folder, this is not necessary. In other words, all folders except thsose sitting immediately under src folder need a companion .rs file. This is not good.

  5. If A sits at a deep level in the file system, and it needs to import some modules sitting at upper levels even the root level, just write it like 'use crate::'.

  6. If A sits at some level, except the root, and it needs to import modules from sibling files, just write 'use super::'.

OK, that's all, but again codes are more than words, it is possible there is something I didn't consider or there are some other ways to do the same work better, if you found, please make some improvements or correct me.

1 Like

Interesting.

But that means when I find I want to use fetcher from button, for example, I then have to go and modify a totally unrelated file lib.rs.

This all seems wrong to me.

You don't need to modify lib.rs for that at all. Either of these lines in button.rs will do the job:

use crate::components::fetcher;
use super::fetcher;
1 Like

Ultimately it's because the compiler starts at lib.rs without knowing about any other modules, and relies on your mod statements to figure out what other source files to look at. This is different from C or C++ where you'd pass all the .c files you want to compile on the command line to gcc or whatever.

(But also what @2e71828 said.)

2 Likes

You can do just that. As long as your lib.rs declares that a.rs, b.rs, and c.rs are a part of your project. And it does that with mod a; mod b; mod c; The crucial idea of Rust's module system is that there is a root module (in this case lib.rs) which (recursively) declares what other modules are included in the project. It is not enough just to have a .rs file somewhere, it must be declared to be a part of the module tree.

1 Like

That sounds about right. I tend to think about it from the other direction, though: I know where in Rust's module tree everything is, and then I figure out where it should be in the filesystem from that.

1 Like

Note that putting files at their “default” paths (i.e., without using the #[path] attribute) has the benefit that any Rust developer working on your project can easily know which filesystem paths correspond to which module paths.

For example, if there is a file src/components/button.rs, it must contain the components::button module, and I can tell this just from its name and location. I find this helps me a lot when contributing to other people's Rust projects.

3 Likes

Seconded!