This part of the book is extremely important; the module system is something
that trips up new rustaceans a lot. I'm trying to re-work it for the
new book, but am not that psyched about what I've done. I think one of
the reasons is that the example is extremely boring, but I've had a
tough time of coming up with something that's simple, but also not
trivial.
What struggles have you had with the module system? What can I do to help here.
The two things that I struggled with the most (and look to be absent) were super, and how it interacts with privacy as well as self, and the differences between use foo, use ::foo, and use self::foo
Why both modules and crates are in the single chapter? I think that the topics are pretty orthogonal and difficult, so perhaps you can have two chapters? One about modules, paths, privacy and directory structure. Another one about crates, libraries, crates.io and perhaps linkers? There is little point in discussing crates if you don't pull any dependencies imo, and you can go a long way with just a single crate.
The suggestion to handle modules and crates separately is a good idea. You might also be able to separate out visibility rules as well though I'm not sure. I'm not sure if having to point out the visibility changes every time you make a module change is a good thing. It kinda hides the rules in a wall of text.
It might also be better to start with modules and build up to crates (not sure how this suggestion would interact with the previous comment). Then, you can just say a crate is a special case of a module with these features:
A
B
C
As it is, you're special casing a crate based on something the reader might not understand yet (modules yet to be discovered). That's just asking the reader to backtrack to remember what the original definition of a crate is when it is mentioned again. Might be hard to remember.
Actually, as it is it isn't very clear on the distinction between modules, crates, and packages from the description. A crate is a special case of a module that only occurs at the top level. A package is then at the top level where it encloses multiple crates. These concepts seem similar and the distinction between them might not be clear.
Also, if you ignored modules from external files for the time being and then covered visibility and private/public separately, you could probably rely on the previously defined explanations when pointing out briefly how modules can be externalized almost as a shorthand.
Re-exports, especially selective re-exports to a goal consumer facing API surface tripped me up for a long time.
For examples, maybe think about pulling in something from std::io or std::net that split across a sys module. Using an actual stdlib example might help give some insight into design patterns of the stdlib, which could be helpful for navigating the source tree for your first few times.
How does pub actually work? How does (pub) mod interact with pub struct|fn|enum? Common use case: how do you make something that you can use throughout your crate but is not exported to users of your crate?
Difference between putting extern crate in your crate root and another module.
The text's explanation is that mod foo; expands to mod foo { /* content of foo.rs */ }. This is of course correct, but I found this explanation not very useful for teaching, because no other languages work that way.
One useful way I found is that mod in Rust is declarative syntax for information usually maintained outside the language in other languages. For example, such information is often maintained in IDE project file or build file (Makefile, etc).
This suggests a less boring example: conditional compilation. An example from std is great:
#[cfg(unix)]
#[path = "sys/unix/mod.rs"]
mod sys;
#[cfg(windows)]
#[path = "sys/windows/mod.rs"]
mod sys;
Here's another:
#[cfg(target_os = "linux")]
pub mod linux;
#[cfg(target_os = "macos")]
pub mod macos;
I was incredibly frustrated and dumbfounded that code in lib.rs is special, and can't be copy&pasted unchanged to other files.
That's because paths in use and the rest of the code look the same, but are interpreted differently. Paths like std::fs::File work in one file, and std would not be found when I tried to move the function to anther file. I haven't seen tutorials explicitly warning about this.
All tutorials assume you write code in lib.rs or main.rs, which in a non-trivial project is an exception rather than the rule.
So please, from the start, show how examples work with minimum two files, because it matters where extern crate is put, and matters how std vs ::std works.
That's what caused me pain until someone explained to me that module references in use and actual code are not the same thing, despite looking the same:
fn foo() {
std::fs::File::open("test"); // OK
}
mod foo {
fn foo() {
// ERROR Use of undeclared type or module `std::fs::File`
std::fs::File::open("test");
}
}