7 ms·
Clear explanation of Rust’s module system
- sitkack 6y agoThis is the single worst aspect of Rust. Can this be fixed in say in a 2021 or later edition?
- ChrisSD 6y agoFixed how? Implicit modules or something else?
- steveklabnik 6y agoWe made a bunch of changes between the 2015 and 2018 edition, so in theory they are possible. It is very unlikely, though, given that means there are already two slight variants. Introducing another has a very high cost.
- monadic2 6y agoHow do you suggest “fix”ing modules? What’s broken about them?
- nercury 6y agoNo, the modules are fine
- n42 6y agoas someone new to the language, reexports have added a not insignificant and seemingly unnecessary level of friction to learning and using new libraries
- burfog 6y agoThe worst is that they keep messing with things outside the language. It is normal to have a set of tools that you like or wish to retain. There may be a package installer (rpm, apt, msi, etc.), a build system (plain Makefile, autoconf, CMake, the Visual Studio project/solution files, etc.), a linker (link.exe, the old binutils ld, gold linker, etc.) and so on. Most of us aren't looking to replace any of that. Even if we are, the stuff coming along with Rust is probably not what we are looking for. Cargo is thus, to many of us, an unwelcome surprise.
- StavrosK 6y agoYour comment is very odd to me, it sounds like Rust is a version of C++ and you dislike them changing the tooling. Where did that assumption come from? I come from Python, and it would be ridiculous if I said "well we already have Poetry so why doesn't Rust just use that". I don't think saying the same thing about the C++ tooling is different.
- burfog 6y agoPython is not comparable. Rust is a natively-compiled systems language that supports the C language ABI. Python, Perl, Java, Lua, Ruby, Scheme, and other such things are expected to bring along some extra gunk. I'm not saying that this is good, but at least it is unsurprising. Rust needs to fit in with C, C++, Ada, Fortran, Pascal, and all the other natively-compiled systems languages. Using generic system-provided tools isn't just for C. You'd likely use a Makefile to compile Fortran. The idea is that the languages are interchangable parts with compatibility. You can create a project that uses a different systems language for each source file, run "make", and it all builds. There is no glue code, no interface definition language, no wrapper, no thunk, or anything of the sort. The expectation is just different. Nobody expects Python to cooperate with Lua or Ruby or Perl, with source files in different languages freely calling into each other. That is the expectation for natively-compiled systems languages.
- wrs 6y agoThis seems like a counterproductive example. The author points out that the module system ignores the directory structure for good reasons, then demonstrates a complicated way to set up a module tree that mirrors the directory structure! It’s just reinforcing the complaint the author is trying to argue against. A better example would show why you wouldn’t want to just automatically derive the module tree from the filesystem.
- Rusky 6y agoBut the module system doesn't ignore the directory structure. It is designed to mirror it! Instead, the author points out that the module system ignores undeclared source files. There are a couple of practical reasons that prevented us from automatically deriving the module tree, as part of the 2018 edition changes to the module system: * Interaction with partial, in-progress programs. People wanted to be able to comment out `mod` declarations to temporarily exclude files from the build. * Interaction with version control, e.g. `git stash`. Similar to the above, people want to exclude untracked files from the build when their corresponding `mod` declarations are stashed. Notably, this is basically the same set of problems people face when using globs in their build system. Maintaining an explicit list of source files can be slightly tedious, but it avoids a lot of surprising behavior in the edge cases around removing files. Rust's list just happens to be distributed throughout the source files, rather than consolidated in a build system file.
- nicoburns 6y agoI don't understand the desire to exclude files using mod. If you are including code from that file via use, then your build will still be broken. And if you're not, then the files would be excluded from the build anyway.
- justinpombrio 6y ago> And if you're not, then the files would be excluded from the build anyway. They aren't though. Even if the module isn't `use`d, it's still compiled. (And it has to be. If nothing else, there could be a trait impl buried in the module, which gets shared without `use`.)
- nercury 6y agoMy suggestion for beginners would be to write modules inline and then use the IDE refactor command (IntelliJ plugin has that) to move them to another file. This is the best way to "get it".
- Fiahil 6y agoThere is a mistake in the screenshots: filesystem view doesn't show `mod.rs`. To all others struggling with Rust's module system, it may seem unnatural if you're coming from another language (like python), but once you finally understand how it's wired it will be as simple as python's. Consider `mod.rs` like `__init__.py`, with the added benefit of being capable of renaming and choosing when to expose internal module components.
- Rusky 6y agoI think that was intentional? Or at least, `mod.rs` is explicitly added later on in the article.
- rkwz 6y ago> There is a mistake in the screenshots: filesystem view doesn't show `mod.rs`. Yes, didn't realize it would be confusing. I've fixed the diagrams in examples 2 & 3 to include mod.rs in filesystem tree :)
- StavrosK 6y ago> Consider `mod.rs` like `__init__.py`, with the added benefit of being capable of renaming and choosing when to expose internal module components. That's exactly what __init__.py does too, though.
- speedgoose 6y agoThanks for the explanation. As a beginner in Rust with experience in a few other programming languages, I think Rust modules are indeed very confusing. I hope they could have something better in a few years.
- deleted 6y ago[deleted]
- flying_sheep 6y agoI read the official document several times and still not feel understanding the module system. But I feel like an expert after reading this :) Thanks Shesh
- Tobu 6y agoSweet and to the point, thanks! Also of note, aside from finding the crate root and dependent crates (these are passed explicitly to the compiler, from Cargo most of the time), all of this is strictly part of the Rust language, and handled by the compiler. I find the language model really clear and explicit. I would not touch it, aside from discouraging the use of mod.rs. I think there's room for an article that expands into Cargo's convention-based source model, which is a bit trickier. A package directory can contain multiple crates: at most one library crate rooted in `src/lib.rs`, but also the various executables that are discovered from the filesystem: src/bin/*.rs, src/main.rs, examples, the test binary, bench binaries… These are crates of their own, with examples and benches needing a library crate to be enabled to access files in the src/ folder (without ugly hacks).
- monokh 6y agoGreat post. Could this get merged to the rust book? Because the guide there was super confusing.
- steveklabnik 6y agoWe have re-written the book chapter many times, and we aren’t making major changes. We also can’t accept big things like this wholesale. I am glad it exists though!
- dbrgn 6y agoThat's a great explanation! However, it misses the part where modules don't need to be in a separate file, but can also live in a "mod { ... }" block within the same file.
- pornel 6y agoI actually like that it omitted the inline syntax. It was the source of my confusion when I read the official Rust Book. Examples with inline syntax don't make it clear that `mod` goes in the parent file (since there's no child file, so you don't have the reference). "But it's the same as if you just put the {} in another file" was like "the proof is trivial, left as an exercise for the reader".
- danellis 6y ago> The module system is explicit - there’s no 1:1 mapping with file system From the examples, it seems like there is exactly a 1:1 mapping with the file system, and it's only the visibility of that mapping that is changed by the `mod` keyword. Am I missing something? > We declare a file as module in its parent, not in itself So I need to declare the `models` module in `main.rs`, even though it's only used by the `routes` module and not `main.rs` itself? To me, that seems like it's breaking modularity.
- burakemir 6y agoThis got me thinking about how "module" - a language concept - is about bringing in definitions (values, types...) into scope, but then there is deployment, something being "installed at a version". Modules, understood as such, are inevitably connected to the stuff that is happening "one level above" where these things providing the modules are installed (Rust crates, npm packages). Reaching for a language-independent word, I'd propose to call these things "components" (slightly longer-form thoughts here: https://blog.burakemir.ch/2020/07/modules-components-and-their-limits.html https://blog.burakemir.ch/2020/07/modules-components-and-the...) ... but those things happen to be called (or heavily associated with) "module" in Java, C++20 and go.
- Animats 6y agoThen there's the "cargo.toml" file, which this doesn't cover. "There's more than one place you have to write it."
- jeffparsons 6y agoThe confusion around Rust's modules reminds me of the different ways that people learn how to use car indicators. Some people learn that you push the stalk up to indicate your intent to turn left, or down to turn right. And then they might eventually learn that in a European car, you push the stalk down to turn left, and up to turn right. And then of course some of these people get confused when switching between different cars. Yes, I've seen plenty of people on the road who indicate one direction and then turn another. It's... kind of frightening. The other way of learning to use car indicators is much simpler: push the stalk in the direction that you would turn the wheel. Of course this still leaves the possibility that some people will push the wrong stalk, and briefly activate their windshield wipers. But it's a much easier mistake to _notice_, and the consequences are minor. Back to Rust's modules. I keep seeing articles trying to offer a simple or clear explanation of how they work, that end up unnecessarily complicated in a way that feels a lot like the "up/down" model of car indicators. The explanation that makes the basics of Rust modules clear to me is this: - Child modules must always be _declared_ in the parent module, or they don't exist. - The content of child modules may be _defined_ either inline in the parent as `mod child { ... }`, or in a file with a relative path of './child.rs' or './child/mod.rs'. Did I miss anything important? Without this basic explanation up-front, I have no idea what to do with the stream of information I'm reading in a lengthy article on the topic -- I've been given no scaffolding onto which to bolt all the details and examples. So this is the "bottom line" that I would like to see "up front" in descriptions of Rust's modules. Other misunderstandings, e.g., around item visibility, are explained really well by the compiler if you get them mixed up, so I'm not sure how much value there is in mixing them in to articles about how modules are _structured_ before those two basic facts are presented.
- Twisol 6y ago> Some people learn that you push the stalk up to indicate your intent to turn left, or down to turn right. I was very confused for a good moment, because the turn signal in my car (which I drive in California) is on the left side of the steering wheel (itself on the left side of the car). I didn't realize the stalk might be on the right side of the wheel in other driver's-side-left cars until you said "some people will push the wrong stalk". The "push the stalk in the direction that you would turn the wheel" method is intuitive to me.
- Someone 6y agoFrom comments, this does fill in some important blanks for some, but IMHO (H=humble and possibly a bit harsh), it’s not a clear explanation because it doesn’t tell all for absolute beginners. I think you’ll lose beginners at “These are the different ways we should be able to consume our modules”. There is no description of what a module is, why they’re needed (or are they optional?), and what that “Cargo.toml” file is about. I happen to know (rightfully or wrongfully) Cargo is Rust’s package manager, but does Cargo require modules (I would guess so, but reading https://doc.rust-lang.org/book/ch07-01-packages-and-crates.html https://doc.rust-lang.org/book/ch07-01-packages-and-crates.h..., it may need packages, but not necessarily modules), do modules require the use of Cargo (I would guess not, but then, why make a discussion of modules harder by adding Cargo-related stuff?)?
- rkangel 6y agoThis is a very clear 'from first principles' explanation. The module tree and filesystem tree picture next to each other are great. The only step in the logic that isn't explicitly explained is why you need to do 'pub mod' as supposed to just mod.