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).