Add a 'use mod' semantic

I had forgotten about this, and I do have some crates like that (to allow integration tests). That is a fourth major use case for "not autodiscover". A solution here (for a new edition) could be to have a separate top level directory instead of putting everything under src, but that feels a bit jank to me.

1 Like

While this discussion turned into module auto-mounting, there's another ongoing discussion about global/distributed item registration. I feel like auto-mounting and global registration are in tension with each other. Having both features together means that items can appear without any reference to them in source code.

4 Likes

IMO this is the actual issue. People don't really get that much confused by mod itself, but instead by the module structure, as they expect to be able to access items in sibling files. This holds for both mod (trying to declare a module from a sibling) and use (trying to import an item from a sibling), it's just that they are more likely to get stuck on the first issue without help from the compiler or IDEs. And if they manage to get past that they likely get enough informations to know how to handle use.

This is how I've seen many beginners being stuck:

  • they get a setup with a main.rs and two sibling files (let's say a.rs and b.rs)
  • they try to use an item from b in a but it doesn't work
  • they read somewhere that they need to declare modules, so they put mod b; in a but this doesn't work
  • they either get stuck here or they discover #[path], which successfully allows them to use b's items in a only to get stuck later when they also put mod b; in main, duplicating its definition.

While it's true that they didn't understood that mod declares a module, and that two mods will duplicate that module, the issue arised from the fact that they wanted to access b's items from a and neither directly referencing them nor importing b worked. With use they would likely get stuck at the same point, since there's still nothing that creates the intuition that both modules can talk only by going through their shared parent. The only difference between use and mod here is that mod allows using #[path] to create an even more confusing situation, where the apparent problem is no longer the fundamental one.

This is not to say that always having to type mod is not annoying because it is, but its removal won't solve this particularly common problem for beginners.

8 Likes

No, I mean the directory containing the main.rs or lib.rs or whatever. The package I was specifically thinking of is, in fact, a multi-crate "workspace" with one (or two, depending on whether you count test-only code) library crates and one executable. As far as I know it is not possible to put out-of-line example code for a library crate outside of the directory containing the lib.rs and still have rustdoc pick it up.

(It's not yet published anywhere or I would link to it.)

Could we use rustc error hints to suggest the correct resolution here? Nothing wrong with having rustc go look at the filesystem to see if a sibling b.rs exists, for the purpose of better error messages only, upon encountering use b::foo in a.rs where there is no a::b module in scope. It would need to mention both that use b::foo in this context means use crate::a::b::foo not use crate::b::foo, and that mod b; must be added to main.rs, not a.rs, to make use crate::b::foo work.

Also it occurs to me that another aspect of the confusion here is that modules from external crates can be referenced via relative paths from any point in the module tree. We're probably stuck with that for ergonomic reasons -- writing use extern::std::fs::File instead of just use std::fs::File all the time would be annoying -- but it definitely makes it harder to develop a coherent mental model of the module tree. (As this thread goes on I find myself less and less confident in my own understanding!) I wonder if there's anything we can do about it.

4 Likes

I think you could do that, and the same with someone trying mod b; from inside module a, however better diagnostics IMO don't solve completly the problem, as beginners often ignore diagnostics altogether.

You can write ::std::fs::File, but almost nobody does except macro authors.

1 Like

I really don't get that, but I had a C++/Python background before going to rust. In those languages you need to look at the errors to figure out what went wrong. When it comes to C++ template errors it might be very obtuse, but if you don't look at the error you have no chance of figuring it out at all.

I don't know what language background we expect people to not read the error messages from. Or is this about people who are entirely new to programming? In which case, you can't approach a programming language like a kitchen appliance. You need to read the manual and check what feedback it gives you.

4 Likes

Over on URLO, it’s extremely common to get questions that either:

  • Contain a screenshot of an IDE with red squiggles and a tooltip that shows only the first line of the relevant diagnostic,
  • Provide a text paste of only the first line or two of compiler output, or
  • Show the entire output, including a relevant help: section that directly answers the question at hand (which was never noticed by the poster)

While rustc’s diagnostics are extremely helpful, they often present as an intimidating wall of text that scares beginners away from trying to understand them. Also, received wisdom from older compilers says to ignore later errors because there’s a high probability that the earlier ones have left the compiler in an unexpected state.

3 Likes

For the -Zscrape-examples feature (active by default on docs.rs) out-of-line examples are pulled from the example crates, which the auto-detected standard is in an examples/ directory sibling to src/. (I really wanted to link an example from docs.rs, but I'm failing at finding any crates where it's actually generated, since there's quite a few situations where it won't).

EDIT: A kind soul on discord managed to find an example example for me, notice that this comes from a file that's not within the src/ directory.

2 Likes

Coming at this from a slightly different direction, I think a huge part of the problem here is that files are always reflected in namespaces, which seems to quite often not be what people actually want.

If I look at https://github.com/rust-lang/rust/tree/master/library/core/src/iter/adapters, for example, that clearly didn't actually want all those files to be distinct entries in the module tree.

Separate visibility, sure. But absolutely not all this:

I wonder what something like a C++ unnamed namespace would look like in rust, and how that might then be reflected in a potential filesystem module system change...

3 Likes

Being able to use the file system for organisation of code would be nice. Especially as there might be multiple associated impls for a type etc. Iter is a good example, but the same pattern comes up elsewhere again and again.

I don't think it could work like C++ anonymous namespaces, as those would be like an inline mod, not a file mod. Also, items in anonymous namespaces are private (to the translation unit in C++, same as static visibility). The closest thing in Rust would be private visibility.

Yeah, not exactly the same.

But an inline version of it would also be nice, like for

mod {
    // no `use super::*;` needed

    pub struct Foo(...);

    impl Foo {
        pub fn new(...) -> Self { ... enforces invariants ... }
        pub fn accessor(&self) -> &Bar { ... }
    }
}

// no `pub use inner::*;` needed

impl Foo {
    ... other methods that can't access privates
}

And then some kind of thing to do that for a single file or folder or something...

I may have missed it in this thread, but would implicit mod also include nested modules?

E.g. could src/foo/bar.rs be imported from src/lib.rs as use foo::bar; without an explicit src/foo/mod.rs or src/foo.rs?

one feature that i think could mitigate this significantly is to only show the highest error level (ie, if there are hard errors, don't show warnings, or at least not trivial ones like "unused import")

additionally, rustc could be configured to only show the first N errors by default. this should reduce the "wall of text" factor, making things easier for new devs, and a bit less annoying for experienced developers (sometimes scrolling up for 5 years to get the actual error is a bit annoying)

1 Like

There is prior art here, that could be built on for an RFC about this: cargo-limit (haven't tested it myself)

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.