Illogical: Cannot use mod in one file but can in another

mod works the same way as fn, struct and enum. It creates a new named item in this place.

You create each item once, and then you import it everywhere else. That's true for functions, structs, enums, and modules alike.

8 Likes

Exactly. I cannot fathom what The Rust Reference § 6.1: Modules is trying to say.

For example. I don't have anything that is obviously a module in my code. I have files full of structs and functions that happen to be public.

Except, I cannot use any of them anywhere unless I put pub mod my_mod; somewhere in my bunch of files. For example in src/lib.rs

So apparently I have a module created from some file somewhere that is actually using my code in some other file somewhere else. Do what?

Meanwhile that reference talks of a mod syntax like so:

Syntax:
Module :
mod IDENTIFIER ;

Where IDENTIFIER is defined as:

IDENTIFIER_OR_KEYWORD :
[ a - z A - Z ] [ a - z A - Z 0 - 9 _ ]*
| _ [ a - z A - Z 0 - 9 _ ]+

RAW_IDENTIFIER : r# IDENTIFIER_OR_KEYWORD Except crate , self , super , Self

NON_KEYWORD_IDENTIFIER : IDENTIFIER_OR_KEYWORD Except a strict or reserved keyword

IDENTIFIER :
NON_KEYWORD_IDENTIFIER | RAW_IDENTIFIER

Nope, no mention of the file name that mod statement is referring to there.

It's impenetrable

You seem to have ignored the subsection I linked to and focussed on the formal grammar at the top, which will tell you exactly what you’re allowed to say but nothing about what it means.

I’ll admit it’s a bit dry and dense, but that’s necessary for a truly definitive document: it has to be extremely precise because there’s no more authoritative place to turn, other than the Rust source code.

1 Like

I don't believe I have ignored anything. I must have read those pages many times in the last year.

Maybe I'm dense but it does not chime with me.

For example:

In order to use code in file A.rs,
from another file B.rs,
I have to put a `mod A;' statement in file C.rs.

WTF?

As an experiment I just removed mod config; from a main.rs in one of my projects. And made a module in config.rs:

mod config {
...
}

Sure enough the compiler complains:

warning: module has the same name as its containing module

So apparently a file is a module. WTF?

I don't see this spelled out anyplace.

The key thing that helped this "click" for me, personally, was learning that:

mod foo;

is just shorthand for:

mod foo {
    /* contents of foo.rs */
}

If you start at main.rs or lib.rs, and expand each mod statement in this way, then you end up with your entire program in a single file, with the module structure clearly visible.

14 Likes

I know it seems really difficult at first, It did for me too, but once I understood it, it became the most convenient way to manage files in a project. It essentially works the same as any regular file structure where you can do /, ./, ../ etc. you just need to remember that main.rs, lib.rs, mod.rs are special in the sense they are one level higher in the hierarchy than other files in the same directory.

In general there's no real restriction where you can import a file from as long as you can draw a public line to it by going up and down the module hierarchy.

1 Like

Thanks but tbh, that looks like in two cases (windows and not(windows)) you declare exactly the same, that is mod foo. Confusing is the least I can say about it.
What would happen if instead of mod foo; one would use mod foo_windows; and mod foo_unix?

The most convenient way of dealing with files is to import them by their relative paths. It is tried and tested, everyone knows it and it is easy to use.
Rust's way on the other hand is at best weird and unnatural.

Then imports would look like this:

use crate::foo_windows::MyStruct;
use crate::foo_unix::MyStruct;

Using the windows import would fail to compile on a unix machine.

It does indeed seem like you declare the same thing twice. The thing is that #[cfg(some condition)] is used for conditional compilation, so the thing it annotates is only compiled if the condition holds. In fact, you can also do this:

#[cfg(windows)]
struct MyStruct {
    foo: String,
}

#[cfg(not(windows))]
struct MyStruct {
    foo: String,
    bar: String,
}

So yes, without the #[cfg(...)] annotations, you really would be declaring the module twice, which would result in an error.

I'll tell you about two recent other languages I used
golang and dart
both of them use strings as paths. now that's generally fine, but I can find more flexibility in rust's way.

for one thing, I can use it to do access control. with using strings you can't only make certain subdirectories invisible. you can make submodules private.

as far as I remember dart's system needs you to define all routes from the project root. you can't navigate like use super::super::module::*
the way I see it, rust's system requires more initial understanding but the result is that rust creates a more controlled system that you can choose to map to your traditional files and folders hierarchy or do it in code. It gives you more control. It is difficult to understand but it is better

garbage collection is tried and tested, everyone knows it and it is easy to use.

1 Like

I don't think you understand the use case of things like that.
You could look at various crates that use either tokio or async-std as the executor. you only have to use feature flags to pick, other than that you don't have to change any code, because the same module can map to different files.

Yes, exactly.

More detailed:
A module is an item that exists in its parent scope just like a struct, an enum, or a function.
You can define a module as:

mod foo{
}

just like you would a struct:

struct foo{
}

Since we don't want all of our code in one giant file, Rust allows you to say:

mod foo;

Meaning module foo consists one of these two: the entirety of the file foo.rs, or the file mod.rs in the subdirectory named foo.

3 Likes

That was all making perfect sense until:

This is really great - thank you @Hyeonu!

Especially the point that you don't need the keyword use at all - it is simply available to shorten path names.

To repeat the summary in Shesh's "Clear explanation of Rust’s module system" blog post:

  • The module system is explicit - there’s no 1:1 mapping with file system
  • We declare a file as module in its parent, not in itself
  • The mod keyword is used to declare submodules
  • We need to explicitly declare functions, structs etc as public so they can be consumed in other modules
  • The pub keyword makes things public
  • The use keyword is used to shorten the module path
  • We don’t need to explicitly declare 3rd party modules

But it seems that since Rust 2018, as documented in the reference manual, the recommendation is to avoid lots of mod.rs files, and use the new module.rs plus module/* directory approach, unlike the examples given in the blog post.

2 Likes

The sentence just means that module foo is defined in either foo.rs or foo/mod.rs. Similar to the explanation in Illogical: Cannot use mod in one file but can in another - #6 by alice

mod aaa{
     fn aaa(){

     }
     mod bbb{
          fn bbb(){

          }
     }
     mod ccc{
         fn ccc(){
         
         }
         mod ddd{
              fn ddd(){

              }
         }
     }
}

is the same as

aaa
|__mod.rs < contains fn aaa()
|__bbb.rs < contains fn bbb()
|__ccc
   |__mod.rs < contains fn ccc()
   |__ddd.rs < contains fn ddd()

this should be fine too

aaa
|__mod.rs < contains fn aaa()
|__bbb
|  |__mod.rs < contains fn bbb()
|__ccc
   |__mod.rs < contains fn ccc()
   |__ddd
      |__mod.rs < contains fn ddd()

notice bbb can be written as bbb.rs or bbb/mod.rs. same for ddd

edit: this works too, and is recommended I think

aaa.rs < contains fn aaa()
aaa
|__bbb.rs < contains fn bbb()
|__ccc.rs < contains fn ccc()
|__ccc
   |__ddd.rs < contains fn ddd()
1 Like

?

Either a module is defined in the current file:

mod foo {
  // module stuff here
}

or the current file says the module is in another file:

mod foo;

Inside the other file:

// module stuff here

The other file is either named foo.rs, in which case it is located inside the directory named after the module the mod statement appears in,

or

The other file is named mod.rs and it is in the directory foo which is under the directory named after the module the mod statement appears in.

I think I might have found things less weird if

mod foo;

Did not exist and pull in whatever file by whatever rules. Consistent with the way

struct foo;

does not pull in struct bodies from some other file. (or does it?)

Why would I ever want my directory tree cluttered up with files called mod.rs? That does not immediately convey any obvious meaning to me.

One might naively expect that any module is written in a file as:

mod my_mod {
    // Lots of stuff.
}

Same like structs, enums, functions etc.

And it is then found and usable in ones other code, with or without any use statements. In the same way as anything pulled in by my a Cargo.toml dependencies.

Does anyone sense the inconsistency here?

Ah well, I can live with whatever it is. I just a bit happy to hear that it was not just me confused by all this.

The problem with that idea is it doesn't permit nesting of modules. So you're basically just concatenating all the files in src together and compiling them as a unit, with no module hierarchy to speak of -- just a flat list of modules, and maybe some other stuff. If you start imposing the directory structure on the module structure, you're going to have to define precisely how that works -- and you probably end up with something similarly complex.

Rust's module system is a bit different from everything else out there. But then, everyone else's is, too :slight_smile: Python modules work a lot like Rust's, but they don't have visibility controls, so you can (to first order) import anything from anywhere. Rust's system is basically what you get if you take Python's idea and graft in the idea of a canonical module hierarchy, so each "thing" (module, struct, function, etc.) exists only at the place where it is declared.

I'm not arguing that Rust's system is the best, or that it's not confusing. I personally like it, for the way it dodges problems other systems have, but certainly a lot of people struggle with it. I do think Rust gets a lot of unfair criticism for not being exactly like X, where X is whatever-I'm-most-familiar-with. The problem with that kind of criticism is that X is different things for different people. Some people want Rust to be like Python, some want it to be more like C, some probably want it to be like Java or Ruby or whatever. But all those things are about as different from each other as Rust is from any of them, so it'd be difficult to synthesize those complaints into anything actionable.

The way modules work is ultimately pretty simple when you break it down, as other people have done in this thread. Maybe we just need to find the right way of explaining it.

6 Likes

The module system was a mystery to me until I worked through the first few exercises of the PingCAP Talent Plan. Maybe that will help.

1 Like