`#[path]` causes issues for beginners

Beginners sometimes use #[path = "..."] when they instead should use normal modules and it causes issues for them later. Of course, this come from a misunderstanding of the Rust module system and if they would read the corresponding chapters on the Rust book they probably wouldn't have those issues. Still, maybe we are able to do better with better documentation (maybe explicitly state in the book you should almost never use #[path]?) or better error messages, or both.

So I ask two questions:

  • Should we optimize for that?
  • If we should, can we? Maybe we can, for example, look for #[path] modules when reporting mismatched types and referring to the chapters of the book about the module system. This is a shot in the dark; I didn't thought it through and it may be impossible/too hard to be worth it.

One thing that could definitely be useful is a lint for including the same file multiple times via #[path]. This is quite rarely what you actually want, and could point new developers towards using the normally-mounted path instead.

6 Likes

I'm curious how beginners even learn about advanced things like #[path]. It isn't mentioned or documented in most books that I've seen. I skimmed through some of the answers on SO and the top results on Google and don't see it mentioned anywhere.

CAD97's recommendation of a lint for duplicate mods from the same file sounds like a good idea. It looks like the duplicate_mod lint in clippy does exactly that? Perhaps it would be worthwhile to check how reliable that lint is, and if it is reliable, perhaps add it to rustc?

4 Likes

That is something I also wonder about. I suspect people just go in the "make the next snippet compile" learning way, which works with some languages but definitely not with Rust.

Didn't know there was such a lint! Problem is, those people tend to not invoke clippy either :frowning: (neither do I, but for different reasons :slight_smile: )

1 Like

It's not in stable yet — note the page says "Added in: 1.62.0".

1 Like

I believe I found out about #[path] from a guide on build.rs generated code, if that helps (getting the generated code into $OUT_DIR)

Personally I have included the same file as different paths as a sort of poor man's parametric modules. If I need to include a lot of code which differs basically in an instance of some type and a couple of consts, the easiest way is often

type MyType = ...;
const MY_CONST: .. = ...;

#[path = "path/to/common.rs"]
mod foo;

This is most common with tests, where something like generics is useless for generalization, since the body of tests is often just a single assertion, while the types involved may require complicated bounds. It's also much more maintainable than a huge macro, since it's treated as real Rust code by the IDE and tools (so e.g. syntax errors are immediately visible).

So it seems that such lints should only fire when super:: doesn't traverse across a #[path] attribute-using module?

I didn't understand what you mean. In my opinion, the lint should only fire if the two modules, with all typed fully resolved, turn out to be identical. This guards against the error of simply including a file twice at different places (though I'm sure that also has its uses), but doesn't fire if the resolved modules are actually different, since the same type/const/fn name resolves to different objects.

The only way (that I can see) that they would not be identical is if they used super:: to fill in some information. Be it types, a function to call, etc. Is there another mechanism which would allow them to be different otherwise (beyond debug traces with the fully::resolved::module::path and #[test] expansions)?