The Great Module Adventure Continues

All right, this thread seems to have died down, so let me try and take a stab at summarizing what people were saying. Let me know obviously if you feel I left something out!

Also available in a gist, where the formatting is slightly better.

The proposals

Let me first re-summarize in a very short-hand form the set of “major proposals” that I’ve seen.

Name Crate-local use External use External in fn body
Today use foo::bar; use std::cmp::min; ::std::cmp::min
leading-crate use crate::foo::bar; use std::cmp::min; ::std::cmp::min
leading-:: use ::crate::foo::bar; use ::std::cmp::min; ::std::cmp::min
leading-extern use crate::foo::bar; use extern::std::cmp::min; extern::std::cmp::min
leading-: use :crate::foo::bar; use :std::cmp::min; :std::cmp::min
suffix-: use crate:foo::bar; use std:cmp::min; std:cmp::min
[crate] use [crate]::foo::bar; use [std]::cmp::min; [std]::cmp::min
[] use []::foo::bar; use [std]::cmp::min; [std]::cmp::min

Here was the post proposing each variant, and a few other notes:

(There was one proposal I saw but did not include, which is this one by @dhardy. I’m not sure that I fully understood it. I think it is saying that we change was use foo::bar means, but still permitting ::foo::bar to be ambiguous, and hence to be leaning quite heavily on fallback – not just for a transition, but forever?)

How to compare the proposals

Based on the comments made so far, I think there are a number of axes on which we can compare the proposals.

The “1path” property

The first observation is that the proposals can be broken into two major category. Most of the proposals enjoy the 1path property, which basically means that the same paths work in function bodies and use statements. This means that we can potentially – eventually – support use foo::bar with the same meaning as use self::foo::bar.

In order to make this transition, though, we have to disallow use foo::bar as legal syntax for an absolute path by the time the next epoch starts.

Note that we don’t have to actually decide whether to permit relative paths in use statements yet. What we do have to decide is whether or not to clear space to permit us to add it during this epoch. We could decide to defer this until the next epoch, and thus allow the current forms and the new forms to co-exist for longer. (Update: See also the note at the end, “on the cost of transition”.)

Amount of transition

Although it does not have the 1path property, the leading-crate proposal does offer the smallest transition from today’s syntax. This because things like use std::cmp::min still work. All other things being equal, it seems clear that less transition is better.

It seems like we can order the amount of transition as follows:

  • leading-crate:
    • crate-local paths change, but external paths do not
  • leading-:::
    • crate-local paths change
    • external paths in a use change, but to a form that is legal today
  • the others:
    • crate-local paths change
    • external paths change
    • the new form is not legal today

It feels to me like the existence of a functional rustfix probably makes the raw amount of change relatively unimportant. It’s hard for me to weight the “feeling” of adopting the existing ::std::cmp::min paths more broadly versus introducing a new form.

@SimonSapin also makes the point that the precise manner of the transition matters a lot, perhaps more than the amount of change. This relates to the next point (coexistence).

Update: See also the note at the end, “on the cost of transition”.

Coexistence

The proposals that introduce a completely new form (everything but leading-crate and leading-::) co-exist smoothly with existing paths, allowing for a simpler, gradual transition. In particular, we can trivially keep all existing paths working, but simply deprecate those that we don’t want to support.

(Note that if we want to pave the way to 1path, we do want to make use statements require the newer forms in the new epoch. As noted earlier, we may prefer to leave that for another epoch.)

There are two to adopt leading-crate or leading-::. I’ll describe in terms of leading-::, but the same applies to leading-crate. The first is the “flag day” approach:

  • Leading up to the epoch, we require extern crate declarations and we stabilize crate-local paths.
  • We issue a deprecation lint for paths that do not fit into the new model, suggesting that you adopt either use ::crate::foo or use ::std::foo.
  • We also issue a deprecation lint for extern crate declarations that use #[macro_import] or other things, preferring instead to use explicit macro imports.
  • When you opt into the new epoch, those extern crate declarations become dead code, so you can remove them.

This has three downsides:

  • you need to make all the changes before moving to the new epoch;
  • you also have to make change after moving to the epoch;
  • we still require extern crate in the leadup to the epoch.

The other approach is the fallback approach. The feasibility of this approach is not yet known; @petrochenkov has indicated it may be plausible.

  • We enable both sets of paths early.
  • When we see an ambiguous path like use ::regex::foo, we first try to resolve in the old style. If that succeeds, we issue a deprecation lint, but allow it to continue.
    • If that fails, we can use the new semantics.
    • Note that name resolution is actually a more complex feedback process, so determining “success” or “failure” can be tricky.

Aesthetic appeal

And of course there is always aesthetic appeal. To some extent, I this is a matter of “getting used to things”, but it’s importance is also not to be underestimated. If people first coming to Rust see what seems to be “heavy” or “ugly” syntax, they may leave before they have a time to get used to it and see it’s inner beauty. =)

Summary of properties

This table summarizes the proposals in terms of the properties above. It’s hard to judge aesthetics, of course, but I’ll note some of the specific concerns that have been raised.

Name 1path amount-of-transition coexist aesthetics
leading-crate small w/ fallback
leading-:: yes medium w/ fallback
leading-extern yes high yes unbalanced, long in a fn body
leading-: yes high yes
suffix-: yes high yes : vs :: confusing, amb. with type ascription
[crate] yes high yes
[] yes high yes
  • The “long in a fn body” comment for leading-extern refers to the fact that something like impl extern::fmt::Debug for Bar feels quite unwieldy. Example comment.
  • The “unbalanced” comment refers to the way that referencing something from an external path requires an extra segment relative to crate-local paths. Example comment; and another.
  • The ": vs :: confusing" comment refers to @arielb1’s observation that, if we ever achieve 1path, then the distinction between use foo:bar; (bar from crate foo) and use foo::bar; (foo::bar relative to local module) would likely lead to confusion.

Observations from the thread

Re-reading the comments in the thread, here are some observations.

A few other things

There were a few proposals for tweaks on “nested” syntax that might go along with each variant:

// Suffix-`:` proposal
use std: { ... }; 

// Leading-`::` proposal
use {
  ::crate::foo::bar,
  ::std::fmt,
};

Suggestion

I’d like to encourage everyone to actually write code using these proposals. I’m tempted to go and implement leading-: and maybe suffix-: as well; I’ve found that writing even a modest amount of code is very helpful to get a stronger feeling.

Updates

I am updating this post with new proposals and notable bits of new conversation.

On the cost of transition. As discussed in this later comment by @rpjohnst and my response, a move to permit relative paths would “repurpose” existing syntax, which comes at a cost in terms of its effect on existing content in the ecosystem. Even if we want to get there eventually, it is probably wise to move slowly, to give content time to adapt: at the most extreme end, we might begin in this epoch by deprecating the “to be replaced” form, so that you only get warnings. Then, in the next epoch, we might make it a hard error initially, and replace it later on. This a knob we can turn and there was extensive conversation about the tradeoffs in the Epoch RFC itself; it seems like, especially initially, it is probably wise not to “turn too fast”, so as to avoid creating the indelible impression that epochs mean massive churn (which is not the intention).

7 Likes