Some more thoughts.
Motivation
Like @nikomatsakis said, the confusion about our module/visibility system extends beyond just new users. While @aturon and I were talking yesterday, he got tripped up about whether an import needed to be prefixed self::
. Outside of this discussion, I’ve seen advanced users (including those who have voiced objections to this proposal) getting mixed up by the dual meaning of pub
.
What we see with new users is a sharp divergence in how hard it is to acclimate to the basic mental model of our system. Some users find it easy to grasp, some find it very challenging. But I think in its full nuance, our current system is ambiguous and confusing for even very experienced users (including those with strong opinions about whether it is good or not). The most accute example of this is the dual meaning of pub
I touched on in a previous post.
Biographically, I began this investigation because of the confusion many new users had about the module system. My goal was not to ‘make Rust easier;’ I was working from hypothesis: the module system doesn’t operate on significant useful complexity (that is, its about organization, not data, behavior, or abstraction). It isn’t doing enough to justify how confusing some users find it.
This isn’t the same as trying to just trying to make Rust easier - its about recognizing a system as more complex than it needs to be (in contrast, something like borrowchecking needs its complexity). I want to make explicit & challenge a certain narrative underlying a lot of the comments which I see as really just a polite form of ‘hackers vs newbs’ framing: the self-identification as ‘UNIX users’, the emphasis on error messages as the solution, the unfavorable comparison made in IRC of this proposal to PHP. I’m drafting this post in vim from an Arch Linux system with a tiling window manager, and I am excited about how this will improve my own experience.
As I investigated what was making the module system so difficult for users, I noticed a lot of other issues I really didn’t like about it. I found it to involve a lot of redundant ceremony that seemed to buy us very little. I found I am frustrated that I can’t find out how visible something is without tracing the modules between it and the crate root, and that the information I need to know about a module isn’t contained within it, but partly in its parent. I noticed that the public-in-private issue is an interminal quagmire.
All of these seem to me like real problems. We can disagree on how we should balance these problems against the problems that changing the system would bring - all language design is about trade offs. But to claim that the system propoesd has no advantages over the current system, or - even more extremely - to claim that the current system has no flaws, is not the discourse of a meaningful technical discussion.
“Junk files”
I’m fairly convinced that being able to rapidly comment out a module is a useful feature, even though I would never leave junk files lying around for any significant period (like, past a git push
). While you’re working, it makes sense to leave files in a poorly typed state, go work on something else, and then come back to it. Certainly you might want to compile everything but the incomplete module in the meantime.
There is another really easy way to comment out a module that is already supported: tag the module#![cfg(ignore)]
(where ignore
is any feature that doesn’t exist). If this is percieved as too much of a ‘hack’ to recommend, we could easily support it as a first class citizen: #![no_compile]
or the like. Similar to the #![internal]
attribute, I like very much that such a technique moves the important information to the module, instead of putting it at the parent.
(This solution has problems if you don’t even want your module to be parsed, but they’re surmountable.)
Backwards compatibility
Backwards compatibility remains a huge open question for this proposal, and like @hanna-kruppe I think we need to start trying to address it. I don’t want to address any specific cases in this post, but I want to lay out a framework for how it can be done.
Basically, the problem seems to be that while most crates work fine with this proposal (that is, they do not have crate trees in overlapping directories), there are many that do not. I went through about 80 binary crates a few weeks ago, and found that about 20 of them had both a library and a binary under src
. About half of those followed a pattern that, in my opinion, we could continue to support, but about half of them simply wouldn’t work with this proposal without restructuring the crates.
(The other common pattern aside from binaries which expose a lib is that of multiple crates in a single directory is a directory full of ‘rust scripts,’ like rustc’s tests. I’m not concerned about the particular shapes, though, just the general principle that some crates can’t be migrated to this.)
The basic principle of how to perform the migration seems like this to me:
- This feature is turned on by some argument passed to rustc.
- If you only have 1 crate root managed by cargo, cargo passes this argument to turn this feature on.
- If you have multiple crate roots managed by cargo, cargo recognizes some directory patterns in which it is safe to turn this on. crates.io’s docs will recommend using these patterns from now on.
- If you’re not following this pattern, cargo will issue a warning to encourage you to migrate your code.
I don’t think we should have a flag in the Cargo.toml for controlling whether this is on or not. I think it should be based entirely on whether cargo determines that your directory structure matches what it considers correct for this feature.