The Great Module Adventure Continues

I think leading-: also can be ambiguous in certain edge cases? The Great Module Adventure Continues

Probably…

Yes, a very good point. It seems like "co-existence" is key here.

1 Like

I don’t like the : syntaxes.
Even ignoring type ascription ambiguities, path being just a homogeneous list of segments is such a nice property.
You can combine paths with segments, paths with paths (see https://github.com/rust-lang/rust/issues/48067 and nested imports) and you’ll still get a path resolved in the same way regardless of how the segments were glued together.

11 Likes

Is there really a "deep" distinction here? For example, we have ::std::foo and std::foo. That first segment (::) is kind of a "special case" of sorts, perhaps no different from :std. That said, I personally lean towards either the leading-:: or leading-crate options, as much for their familiarity as for any other reason, so perhaps it's a moot point.

@petrochenkov, how hard would it be to implement the "fallback" semantics for absolute paths, so we can test the impact? Would it be reasonable to assess said impact by doing a crater run? (i.e., to ensure that all existing code still works)

1 Like

I don't think coexistence in the sense of your summary post is quite that important for preserving existing documentation, actually. Presumably we don't want people to actually use the old syntax, so "just" making that documentation work is insufficient. There may be lints and later error messages that guide the transition, but it would be better just to make the delta smaller so the documentation is already using the new form as much as possible.

This goes along with a lot of the anxiety around having epochs at all- the easier it is to read code from a different epoch, because you're spelunking in some transitive dependency nobody's touched in years, the better. The fewer subtle differences to trip on when an epoch isn't your main focus, the better. This is part of why the RFC went the way it did, and this is what we lose by going for properties like 1path, which is part of why I don't think it's that helpful of a property.

1 Like

I think we can distinguish four cases:

  • Older syntax is still the right syntax.
  • Older syntax still works as it ever did, but is deprecated (you get a warning).
  • Older syntax errors out, but you get some good advice on how to fix it.
  • Older syntax has been repurposed, and hence now does something different.

All other things being equal (which of course they are not), these are in order of preference. The second one is what "co-existence" gives us -- it still seems like a useful distinction.

I definitely think we should not "jump" to permitting use foo::bar as a 'relative path', even if we lay the steps that permit us to do so later. But laying those steps would mean that -- for such syntax -- we would expect an error, but hopefully with clear directions for how to fix. So we'd be at the 3rd rung.

(There is even a slower path, of course, where we don't make anything an error, but we reserve right to do so in the next epoch, if we so choose. This may be wise, and moves everything up to the second rung.)

3 Likes

I don't know until I try. Should be simpler than "macro modularization" (subset of Tracking issue for "macro naming and modularisation" (RFC #1561) · Issue #35896 · rust-lang/rust · GitHub), which probably laid necessary groundwork.
Certainly not hard for @jseyfried from the parallel universe in which he has time :slight_smile:

1 Like

I didn’t follow all the discussions since last time this came up but is this syntax completely off the table?

from crate use path

What I like about this is that it cannot be used “inline”, i.e. importing an external crate sticks out. Still, I’d say that the 1path property applies somewhat, if only the path is considered, not the crate name.

But if this has already been ruled out, I don’t want to revive the discussion.

1 Like

Yeah, any sigil at the path start (:a::b) is probably not worse than existing ::a::b in this sense, it will turn into {{root}}::a::b anyway.
(Note: representing absolute paths using an extra segment turned out to be such a great idea in the hindsight).

Really like the table and structured approach to evaluating goals here!

Personally I think leading-:: is the most attractive in the long term, followed by leading-crate. I guess a lot might depend on whether the fallback thing works out.

Was the [krate]::foo::bar syntax decided against? It's semantically equivalent to the rest of the use extern::krate::foo::bar syntaxes and feels more natural than the already-overloaded single colon while being less heavy.

Having use foo:bar; (or use :foo::bar;) and use foo::bar; refer to 2 similar but different things feels like it would raise "when do I use a single colon and when do I use a double colon" confusion.

Of course, if we can have a good enough fallback story (which I am not that sure there is - IIRC there was a problem because use foo; can either crawl in the filesystem to find foo or import a module foo) then using a crate::-style syntax might be a better choice to reduce the impact on examples.

4 Likes

I somehow like ‘leading-crate’, but the difference between 'External use’ and ‘External in fn body’ is really irritating.

The beauty of ‘leading-::’ is, that there’s no such difference, but ’::crate’ feels redundant.

I don’t like the variants with ‘:’, the visual difference between ’:’ and ‘::’ is IMHO too low.

There’s something about the ‘leading-extern’ variant. Yes, it’s a bit more noisy, but it’s pretty clear and consistent.

Very good point.

1 Like

Not definitively. I will extend the table.

Updated the summary to include [crate]; also included a note about the conversation that @rpjohnst and I had regarding the cost of transition.

Recording my preferences:

  • I think the 1-path property is very nice to have. While it doesn’t have to be an essential goal of this work, I fear that the cost of any churn is probably not justified if we miss out on it.
  • I find the : vs :: distinction very subtle, I’ve been stumbling on it in this thread, never mind in a real code. I’m pretty strongly opposed to any scheme which relies on a semantic difference based on : vs :: especially inside a path (as opposed to as a prefix)
  • I use :: in my code more frequently than many, and I don’t find it odd at all, it feels like a natural way to distinguish between absolute and relative paths which is coherent with the rest of the path syntax and fairly lightweight.
  • I would prefer a scheme which encourages one block of imports per crate, rather than one block for the local crate and one block for all external crates (thinking about imports in Python and JS, one block of imports per crate feels like it would be easier to read quickly).

Given all the above, my preference is for leading-:: I would also like to allow relative paths in use statements, eventually.

8 Likes

Important to remember that your syntax highlighter will hopefully help you in real code.


I also prefer :: in the abstract - it seems like the obvious solution. But I'm worried about the fallback & transition story. I think its not that uncommon to have an extern crate and a module at the top level with the same name - usually because the extern crate is an optional dependency and that module contains all the code relating to it. In fact, failure has just such a module. So if the story around this isn't smooth, its not hypothetical - people will actually be inconvenienced.

I'm generally open to any 1-path solution except for extern, just because it is far too long. This isn't only because inline use is important, but also because I don't want to see a single nested import become necessary or the idiomatic default because the alternative is too noisy. The fact that imports/mod statements have a very different shape from code is very useful to me when I'm visually parsing a file of code - it tells me where to start reading. If imports have a braced and highly nested structure, they'll start to look like types and functions and that will frustrate me. (I'm not against nested imports as an option when they're suitable, I just think the idea of always importing everything from std with 1 use statement is actually a loss, not a gain).

3 Likes

I actually like ::crate, because it means all use statements start from the same root level (of the crate registry universe).

1 Like

I actually like ::crate, because it means all use statements start from the same root level (of the crate registry universe).

The crate might not be part of the registry universe (crates.io).

The prefix '::' is a stronger visual marker if it only references to external dependencies.

Having a separation of internal and external modules by the prefix - '::' and 'crate' - seems IMHO a lot more clear than having '::' and '::crate'.

2 Likes