Separating Modules into Different Files

Hi, I have separated my crate in different files using format

src/lib.rs
src/databas.rs
src/database/do_first.rs
src/database/do_second.rs

As was recommended in Separating Modules into Different Files
Here was also mentioned here are old style of using mod.rs file.

  • src/front_of_house/hosting/mod.rs (older style, still supported path)
    I assume if it is "still supported path" in future it may stop be supported.

Now I am trying to create some integration tests. I have created

tests/integration_tests.rs
tests/common.rs
tests/common/first.rs

in common.rs I put "pub mod first;" but I get error: unresoled module, can't find module file: first.rs or first/mod.rs
from error it looks integration tests only use old style of paths. In Integration testing here also was used old style of tests/common/mod.rs

I get error using Visual Studio Code version 1.79.0

My question would by why integration tests don't support new paths and if old paths will be removed from rust at some time, will it not mess up integration tests?

That's an interesting way of looking at it. I hadn't thought about it like that before.

The way I think about this is that module file name rules apply uniformly everywhere except to the "root" module of a crate. Consider that if it worked the way you expected it to, your crate's source files could be organised like so:

src/lib.rs
src/lib/database.rs
src/lib/database/do_first.rs
src/lib/database/do_second.rs

Each .rs file in the tests directory is treated as its own root module, and so follows the same rules as lib.rs or main.rs do: lookup starts in the same directory, always.

As for why... my guess would be either no one considered it because applying those rules to lib.rs would look very strange and there was no appetite for it, or because there is some edge case where you could have existing code that becomes ambiguous under the new rules.

Also, I really doubt the old style is going away. I mean, I certainly hope not: I'm still using it on new projects! They'll take old module names when they pry them from my cold, gnarled, mildly inconvenienced fingers!

As an aside, I usually fix this problem like so:

// common.rs
#[path = "common/first.rs"]
mod first;
1 Like

The old style is not deprecated and will not be removed. Sometimes it makes more sense for the module root to be inside the folder, and then it's perfectly fine to use a mod.rs file.

This is not quite what is going on. There is a conflict, but it isn't about integration tests themselves refusing to use new-style module paths. Rather, it's target auto-discovery getting in the way. In particular, auto-discovery looks for any files named either tests/*.rs or tests/*/main.rs and treats those as binary crate roots (in exactly the same way src/main.rs is).

Thus, Cargo attempts to compile tests/common.rs as its own integration test. That then fails because modules of a crate root are always (in both old and new style) looked up as siblings of the root file, so mod first; in the crate root tests/common.rs expects to find tests/first.rs.

There is a subtle hint to this in Cargo's error output:

error[E0583]: file not found for module `first`
 --> tests/common.rs:1:1
  |
1 | mod first;
  | ^^^^^^^^^^
  |
  = help: to create the module `first`, create file "tests/first.rs" or "tests/first/mod.rs"

For more information about this error, try `rustc --explain E0583`.
error: could not compile `scratchpad` (test "common") due to previous error

Notice that the last line (which is from cargo, not rustc) says (test "common").

That's telling you that it is compiling a test target named common, which isn't what you wanted. Whenever you get really weird errors about missing files or items, this can be something worth checking for.

But in this situation, your tests/integration_tests.rs will actually work just fine if you run it because it's a separate test target. It's being compiled exactly as you expect; the troublesome part is the extra unwanted target.

Here are some solutions:

  1. If you don't actually need more than one integration test target, the simplest option is to use the multi-file target layout:

    tests/integration_tests/main.rs   // mod common;
    tests/integration_tests/common.rs
    tests/integration_tests/common/first.rs
    
  2. As you concluded already, you can use the old-style module path for the root common module, to avoid the conflict:

    tests/int_1.rs   // mod common;
    tests/int_2.rs   // mod common;
    tests/common/mod.rs
    tests/common/first.rs
    
  3. Move your shared testing code into your lib.rs. Advantage: it will be compiled only once, not once per test target. Disadvantage: it adds compile time and maybe additional dependencies to your library target.

  4. Disable target auto-discovery and explicitly list each of your tests. (I would not recommend this, because sticking to the auto-discovery-compatible layout means people reading your code can also understand how it is being compiled without having to read your Cargo.toml first.)

3 Likes

Thanks.
Now I think I understood. The main problem is how target auto-discovery works on src/ and tests/ folders.
In src/ folder only main.rs or all *.rs files in bin/ folder is binary crates. In tests/ folder every *.rs file is binary crate, so here are no modules. Modules can only be created using old path style with mod.rs file.

If tests are not remade old path style will not be removed. I also don't see reason to remove old style as it is convenient, because module is in one folder. New style module is made from on .rs file and one folder of same name.

src/bin/*.rs (and examples/*.rs and benches/*.rs) all have the same potential conflict as tests/*.rs. Only src/ is different because Cargo doesn't look for arbitrary *.rs names in src/.

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.