Rust: Tracking issue for RFC 2126: Clarify and streamline paths and visibility

Created on 17 Sep 2017  ·  120Comments  ·  Source: rust-lang/rust

This is a tracking issue for the RFC "Clarify and streamline paths and visibility" (rust-lang/rfcs#2126).

Steps:

Unresolved questions:

  • How should we approach migration? Via a fallback, as proposed, or via epochs? It is probably best to make this determination with more experience, e.g. after we have a rustfix tool in hand.

  • The final syntax for absolute paths; there's more bikeshedding to be done here in a context where we can actually try out the various options. In particular, there are some real advantages to having both crate:: and extern:: paths, but ideally we could do it in a more succinct way.

B-RFC-approved C-tracking-issue E-needs-mentor T-lang WG-compiler-front

Most helpful comment

Here's another instance I just ran into that combines the crate keyword in both a visibility modifier and a path, that seems even more likely than the crate use crate... instance.

Current:

pub(crate) struct Bar {
    pub(crate) foo: ::Foo,
}

pub(crate) struct Baz(pub(crate) ::Foo);

With changes from this tracking issue:

crate struct Bar {
    crate foo: crate::Foo,
}

crate struct Baz(crate crate::Foo);

I personally find the new one to be more confusing than the current way.

All 120 comments

That RFC has 4 distinct features and yet we still only have one tracking issue for all of them. Can we please not shove together disparate features like this?

@retep998 As explained throughout the RFC discussions, these features are connected through global design considerations. E.g., providing an external mechanism for renaming crates is motivated in part by the eventual deprecation of extern crate. We can and will gate various aspects separately (and will likely have some overlapping gates for trying out different syntaxes), but for discussion on the overall design and stabilization, it's important to keep global coherence in mind.

Having an external mechanism to rename crates is something which has already been desired and needed for years (https://github.com/rust-lang/cargo/issues/1311) and could have stood alone just fine, but instead it's being used as nothing more than a pawn to support killing off extern crate.

We've had no problems with having separate RFCs for closely related features in the past (the RFCs for repr(align(N)) and repr(packed(N)) come to mind), yet now we're claiming that changing foo/mod.rs to foo.rs is so closely related to extern:: and crate:: that they have to be in the same RFC and use the same tracking issue?

Just to make sure this point sticks around, since it's a relatively subtle aspect of the syntax unresolved question: Using crate as a visibility modifier as well as a path prefix introduces a parsing ambiguity between crate ::absolute::path and crate::relative::path. Using a contextual keyword introduces the same ambiguity, and there are no other reserved keywords that really make sense as visibility modifiers.

I would thus like to (at least) experiment with leaving out the crate visibility modifier and sticking with the existing pub(crate).

I wouldn't mind splitting the foo.rs/foo/mod.rs point into a separate tracking issue, since it truly does seem independent of the path and visibility modifier changes.

As far as external crate renaming... there already is a separate issue for it? It is pretty important for the path changes so I think it's fine being part of this one as well.

Despite lots of discussion a few weeks ago regarding the choice of crate as a visibility modifier (not a path prefix), I'm disappointed to see that although this choice of keyword was listed as an 'unresolved question' in the RFC, it has now been apparently forgotten. I myself, and several others I've noted, find this choice confusing since it isn't an adjective/modifier, and can also be ambiguous: does crate mean part of the crate's public API? No! It means local or internal or pub(lished) to the crate (hence why I prefer the latter keywords). So I'm not demanding immediate change, but at least acknowledge it as an unresolved question in this tracking issue, so that it is not forgotten upon stabilisation.

It's great that this module redesign has progressed so far, but at the same time it's important that we don't blunder ahead just to make the 'impl period', and end up making decisions without consulting the majority of Rust users. And, unfortunately, I think the people involved in Github RFC discussions aren't representative of the whole user base, because of the sheer flood of information/comments/opinions there can be discouraging. So somehow that needs to be dealt with too.

It isn't clear how the implementation ended-up...

@rpjohnst @retep998 I have opened a new RFC to discuss foo.rs + foo/ as well as propose a few improvements to this RFC.

Edit: I suggest we open another RFC to discuss crate as a visibility modifier. Personally I would like to see the opposite: pub(extern) be added and required for all externally published symbols. This would make bare pub be the equivalent of pub(crate) in the next epoch.

@rpjohnst

introduces a parsing ambiguity between crate ::absolute::path and crate::relative::path

Isn't crate ::relative::path invalid code anyway? Can't it be rejected during parsing?

@est31 No, that requires doing name resolution during parsing which is definitely not something we want to do. It's one of the more annoying parts of parsing C and C++, which have similar ambiguities around a * b being a multiplication or a declaration, and around a b(c, d); being a variable declaration or a function prototype.

If we ever start allowing dependencies and top-level modules with the same name, even tangling up name resolution with parsing won't save us- crate :: some :: item could be a crate-visible item from dependency some, or a private item from top-level module some.

To keep this in perspective, we could just arbitrarily resolve the ambiguity one way or the other and require parentheses to write the other case (probably the crate-visibility absolute path case, which seems rarest), but that's still a foot-gun that we don't need if we stick with pub(crate), which already solved the syntactic ambiguity.

Using crate as a visibility modifier as well as a path prefix introduces a parsing ambiguity between crate ::absolute::path and crate::relative::path.

This is a tiny issue, IMO, given that both visibilities on tuple struct fields and "inline" absolute paths are rare.
Paths are always parsed greedily currently, so it makes sense for crate :: x :: y to mean crate::x::y.
If the opposite meaning is wanted, then pub(crate) ::x::y or crate (::x::y) can be used.

@rpjohnst @petrochenkov could you please share a code example where pub ::relative::path is valid code? I don't get the entire ambiguity thing because I think one of the two cases is invalid.

Edit: the only place where this could be relevant is inside macros when you match for a visibility qualifier + a path. But that's a very small breakage so acceptable IMO.

@est31 You seem to have the wrong idea- it's not about relative paths, they're never an issue. It's about building the AST before you know what any of the names refer to. Here's a full sample:

struct S(crate :: x :: y);

Given that you're not allowed to look up any of those names yet, how do you convert that string of characters into an AST? There are two possible answers. One has a private field of a type y defined in module x of the current crate. The other has a crate-visible field of a different type y defined at the top level of dependency x. No macros needed.

@rpjohnst I see thanks for clarifying. Its indeed an ambiguity.

@rpjohnst

A simple solution is to define it unambiguously parse to the crate visibility modifier. If you want to parse it as a tuple struct with private member of the given type, do it like this:

    struct S(:: crate :: x :: y);

(note the :: prefix to indicate the root 'namespace')

This is consistent with how other root namespaces need to be referred to in submodules (eg ::std::x::y).

Just disambiguating to crate-as-a-visibility would be somewhat surprising, I think. But it might be a good idea to "canonicalize" that workaround a bit and enforce that crate:: is always used with a leading :: (outside of uses of course). As you point out, this increases symmetry with ::std::x::y-like paths, and leaves the un-prefixed form for truly relative paths (self::/super::/something_in_scope::).

Should we do this? Allowing crate::x::y in non-use paths makes crate kind of magical, while enforcing ::crate::x::y scopes it to the same level as dependencies, which is what we're trying to imply anyway.

@rpjohnst

enforce that crate:: is always used with a leading :: (outside of uses of course)

This may be reasonable, at least for a start (nothing prevents relaxing this later).

I'm not a fan of the use extern::bar::foo or use crate::bar::foo syntax. It seems very noisy. I'd prefer some sugar for this. I suggest extern bar::foo for this.

How about add one more implicit rule? Automatically import extern crate into root namespace, make a more coincident path expression.( I'm not good at English, pls learn my view from the following example)

e.g.

  1. Our project structure like this:

src
|--lib.rs
|--foo.rs
|--foo
|----|--bar.rs

  1. you config a dependence in Cargo.toml, like
    [dependencies] serde = "3.0.0"

  2. we add mod bar into mod foo, and add mod foo into lib.rs,

  3. we define a function finlib in lib.rs, define a function finfoo in foo.rs, define a function finbar in bar.rs

Make the extern crate's scope as the top level module in crate, then we can write code like this anywhere.

for fully/qualified path

::serde::Deserialize
::serde::x::y
::finlib                            // not ::crate::finlib
::foo::finfoo                   // not ::crate::foo::finfoo
::foo::bar::finbar           // not ::crate::foo::bar::finbar

relative path write like this

serde::Deserialize      // no need to write `use serde`
serde::x::y                   // no need to write `use serde`
finlib                          
foo::finfoo                  
bar::finbar       

we first look up serdefinlibfoo in current mod scope, if not found lookup in supper mod, until the root namespace. if there have name conflict, we write fully path instead.

also we can use self::bar::finbar in foo.rs to avoid name look up.

I'm not able to find the earlier thread that discussed this, but the compiler used to work that way and it caused major problems for name resolution. IIRC @pcwalton or @arielb1 probably know more.

Wouldn't the easiest solution to this ambiguity be to use a different keyword for the crate-local visibility modifier (ie: thepub(crate) replacement). For example, local, as suggested many times before in previous RFC discussions. Then struct S(local :: x :: y) is distinct from struct S(crate :: x :: y).

That just introduces another ambiguity between <visibility> <absolute path> and <relative path starting with the new keyword>, since the new keyword would have to be contextual.

@rpjohnst ah damn.. But isn't that a problem the epochs were designed to solve? Eg: in the current epoch we just use pub(crate), and then, in the next epoch, introduce a new non-contextual keyword that is more 'ergonomic'.

@neon64 yes but its a claim by the RFC that it doesn't require a new epoch. That claim seems to not hold.

It holds just fine- there is currently no code that uses the crate visibility or crate:: paths, so only new code will be affected. As long as we pick a resolution and stick with it there's no compatibility problems.

One thing that's occurred to me, with apologies if it's been brought up before in one of the discussions (I don't recall having seen it in the last couple):

In the latest proposal, we have crate:: to refer to "in this crate" and self:: to refer to "in this module". This is slightly inconsistent and potentially less clear than it could be: for one thing, where self refers to "this", it's not inherently obvious "this what", and conversely, from the fact that there is a self:: which means "this module", one might infer that crate:: could mean "some other crate", which is a confusion that has in fact been mentioned in the thread.

One possible solution would be to phase out self:: in favor of mod::. Then we'd have crate:: to mean "in the nearest enclosing crate", and mod:: to mean "in the nearest enclosing module", and it would be clear and consistent. We could also potentially solve the issue where it's not possible to refer to items within an fn scope in a qualified way at all by introducing an fn:: prefix, to unsurprisingly mean "in the nearest enclosing fn". (I haven't thought about whether it might make sense to go further, and also have things like trait:: or impl::.)

@glaebhoerl that's an interesting proposal. Personally, I think I have come around to the idea of introducing a new syntax for absolute paths and deprecating the existing one. I'm not sure what this syntax should look like, but I will describe one possibility below (that I know has been floated before).

Imagine we had the following grammar for paths:

Path = AbsolutePath | RelativePath
AbsolutePath = 
    | `@` ID? 
    | `@` ID? `::` RelativePath
    | `self` :: RelativePath
    | `super` :: RelativePath
RelativePath = ID (`::` ID)*

Under this grammar, one would reference things from other crates by beginning with @crate, for example:

use @std::collections::HashMap;

One would reference the local crate with just @, for example:

use @::something::in::my::crate;

(And use self::something remains same as today, for better or worse.)

Something nice about this is that absolute paths are completely distinguishing from relative paths, which means we now have the desirable property that one can copy-and-paste a path from a use into code and it works:

fn foo() {
    @std::cmp::min(a, b) // OK
}

This is not true today, which definitely confuses me from time to time.

We also eliminate the parsing ambiguity around crate, so you can do pub Foo(crate @::MyType) or whatever and it works fine.

(In general, having relative and absolute paths start with the same :: has been a source of pain many times, is the truth.)

One thing I don't know is whether @foo is the best. The other option that I think works and which I've thought of is []:

  • [std]::collections and [crate]::collections // []::collections seems too weird.

I really don't think we ought to introduce any new sigils just for paths. Enforcing a leading :: in ::crate::foo is enough to address @glaebhoerl's comment and to eliminate the parsing ambiguity. It also fits the intended mental model for crate:: more closely- it's a stand-in for the name of the current crate, not a path prefix like self::.

(In general, having relative and absolute paths start with the same :: has been a source of pain many times, is the truth.)

I'm not sure what you mean by this. I don't think any relative paths start with ::, do they?

@nikomatsakis @glaebhoerl independent of whether I like your suggestions or not (I actually can live with both), can we please stick to the RFC? It has been discussed back and forth in plenty ways and I don't think that opening up debate again would help anybody. A total of 82 people have commented on the thread of the latest RFC (there were quite some discussions before), many feeling very strongly about it. I think it would be unfair to them to sneak in a last-minute change of the proposal at this point, without giving them a chance of reviewing the change.
Especially I'm a bit confused because in #44721 @nikomatsakis shut down discussion about the feature with the argument "let's only talk about the implementation please".

The @ notation and [] notation have both been proposed (with me being a supporter of both) but they eventually didn't make it, at least that's my impression, due to negative responses from users.

Even if there are no cases at all where a leading :: can be confused with a path that starts on a preceding identifier and has an internal ::, it isn't very distinct visually.

Also, it's less suggestive. @ is a better mnemonic. With a leading :: you have to wonder whether it's like filename paths, where leading / means root (if you're on a Unixy system), or if it's a standard abbreviation, where no leading thing would mean "we left the name out because it's relative". With a moment's thought you can see that the former makes more sense, but the point is that it takes a moment's thought. @ (or somesuch) is instantly distinguishable to both the compiler and the coder, making it easier to instantly grasp what is going on.

How is a leading @ easier to distinguish than a leading ::? :: is already an established mechanism both in Rust and C++!

@rpjohnst - I said how in the very sentence you are responding to! @ is distinct visually. To expand: it jumps out at you. There is no danger of leading vs. internal positioning being confused in your mental model of the token stream.

@ is a completely new token with no meaning whatsoever, and would take far more than "a moment's thought" to figure out. Confusing a leading :: for an internal :: isn't a problem to begin with- there is no reason anyone would have to take "a moment's thought" to dismiss the idea that a leading :: might be a relative path.

@rpjohnst - Your statements are true given sufficient training on what :: means (which is provided by experience with C++ or Rust). I was speaking of learnability and intrinsic difference. "The same symbol used in a different context" is never going to be as distinguishable as "a unique symbol".

I could accept an argument that it's not worth burning @ as a unique symbol on a use case like this where there is a tolerable alternative.

@Ichoran: I think that adding new token for a certain semantics (@) to the Rust grammar is not a step to be taken lightly, and I have a slight suspicion we are too close to doing that with @nikomatsakis and others' proposal. For one, because we then cannot reuse it for another purpose later in the life of the language (I suppose paths are so pervasive in the code that the @ token could appear in many places). I also wonder what the effect is on the perception of the complexity of Rust code. To a novice not used to e.g. C++, I believe (but don't have research on this) Rust is language with a quite baroque, intimidating notation. One of the strategic goals for this year is to optimize the learnability and I suppose approachability of Rust. So maybe we have to consider this when fancying @ because the intuition is it would stand out in code (which @rpjohnst I think rightfully questions). Am I clear enough what I mean with this?

(Feel free to correct or clarify anything I claimed, since I simply do not have time to follow the discussion on the Rust grammar all that closely.)

@est31

independent of whether I like your suggestions or not (I actually can live with both), can we please stick to the RFC?

Basically I agree. I don't really want to get involved in big discussions right now -- it's the impl period after all! -- but I did want to put the @ idea "out there" to be simmering in the background. In part, because I tend to get kind of forgetful, and writing things out helps me to remember them later.

My feeling is that at this point the right thing to be doing is:

  • Implementing the RFC roughly as written.

    • We may want to, for example, adopt @rpjohnst's suggestion of ::crate for absolute paths, just to resolve the parsing disambiguity, because it seems like a very small delta, and actually increases consistency overall.

  • Gain experience in using it.
  • Revisit the design, keeping in mind alternatives that have been floated in the meantime, and make final stabilization decisions.

This was the spirit in which I meant to write my comment, though I guess I didn't make that clear. Perhaps it would have been better to keep it in a private file. :woman_shrugging:

EDIT: Also, I'm aware that @ and [] were raised earlier, though I don't recall whether the @::foo notation was mentioned before for being relative to the current crate. So I don't mean to claim authorship of the idea.

OTOH, the idea I mentioned had not been raised previously, and is not so much a modification to the RFC as an extension of it. Also, "The final syntax for absolute paths; there's more bikeshedding to be done here in a context where we can actually try out the various options." is explicitly listed as an unresolved question in the issue body. I agree that, in general, we should try to avoid re-litigating accepted RFCs, but I don't feel this situation is comparable to the one we had with e.g. inclusive ranges (ugh).

See also this discussion thread:

https://internals.rust-lang.org/t/the-great-module-adventure-continues/6678

Welcome to another episode of the Great Modules Adventure! When last we met, our brave adventurers had at long last come to the fabled land, having accepted RFC #2126. There they took a brief respite, and made ready to begin with the Impl Period. In that period, a great much work was done, and indeed the outlines of the module system came to be implemented. But lo there were a few niggling questions to resolve still… and that brings us to this discussion thread.

In less flowery language: during the impl period, we made a lot of progress implementing the “Clarify and streamline paths and visibility” RFC (along with a number of related RFCs). In fact, we made almost too much progress – we implemented a couple of different variants, and I’d like to get a discussion started about what we really want.

I'd like to be able to stabilize this excellent work some day =) and that will require us picking one of the variants...hence the thread.

Personally I'm a fan of the @ syntax mentioned by @nikomatsakis. It's concise, and reasonably self-explanatory. We're going to have to introduced some sort of new syntax here, so better a special char than a reserved name like crate (ugh).

Looks like we're in a position to tackle the second outer pointer now, especially given https://github.com/rust-lang/cargo/issues/1311 is now resolved (it should be closed already, in fact). I could possibly tackle this, with a bit of mentoring, and a decision on @ vs. crate syntax. Thoughts?

Added tracking issues for the two unimplemented lints.

It occurs to me that we have a small compatibility issue: if we are to have extern crate become implicit, there's a breaking change involved in making that work -- extern crate forces crates to be linked; which can cause errors if you specify extra crates in Cargo.toml but not lib.rs that do not link together cleanly (e.g. panic=unwind crates with panic=abort, or crates that export the same symbol). Have we determined this to be a non-problematic breakage?

It occurs to me that we have a small compatibility issue: if we are to have extern crate become implicit, there's a breaking change involved in making that work -- extern crate forces crates to be linked; which can cause errors if you specify extra crates in Cargo.toml but not lib.rs that do not link together cleanly (e.g. panic=unwind crates with panic=abort, or crates that export the same symbol). Have we determined this to be a non-problematic breakage?

The only solution I've seen to this so far is to only implicitly link a crate when you import something from that crate, however this still has some problems. There's a lot of ways that crates can expose functionality in ways other than simply exporting rust symbols to be imported, such as linking in a certain native library or exporting #[no_mangle] symbols to resolve some dependencies in a native library. Once extern crate is removed, the only way to force such a crate to be linked in is to make it export a placebo rust symbol to be imported as needed. I don't know who thought it was a better idea to force people to use such a workaround instead of sticking to extern crate.

Yeah, we explicitly use this pattern in Firefox -- there's a toplevel gkrust crate that just contains extern crate statements for crates that need to be linked in (these expose extern C functions written in Rust that Firefox calls)

You could still make it work with requiring people to use cratename; in lib.rs to force this.

What about having a #![link_crates(stylo,webrender)] attribute for this purpose? Unlike extern crate, it wouldn't add the crate to the name tree. Giving it a new name would clearly indicate to readers that you are including the crates for linkage only and that the statement should not be removed, while extern crate should.

That solves the opposite problem though?

Oh, I see, for people on the 2015 epoch it lets them upgrade their code without switching epochs.

But this means that _everyone_ would need to use a link_crates key. That's not ideal.

What about having a #![link_crates(stylo,webrender)] attribute for this purpose? Unlike extern crate, it wouldn't add the crate to the name tree.

What about crates that could be used either as link-only crates, as crates with Rust symbols, or both?

@whitequark if you use symbols from those crates, you'd get linkage for free like it is the case already now. The attribute would be on the usage site so you as user decide how to use the crate.

@Manishearth

But this means that everyone would need to use a link_crates key. That's not ideal.

No. I haven't phrased my proposal well enough. I want to keep the part where use cratename::item; has the same effect as link_crates intact. The link_crates feature should only be used if you don't need any item from the crate but need the linkage. If you import any items from the crate, you'll get linkage like how the RFC proposed.

I just don't think that having use cratename; in your code for that purpose is good because it would confuse lints and (most importantly) code readers/writers, and thus think that there should be a dedicated feature for that legitimate (but seldom) use case.

@est31 Bikeshedding on the syntax for link_crates, instead of having it as an attribute, why don't we do something like extern crate foo;?

@retep998
Good idea! :smile:
https://github.com/rust-lang/rfcs/pull/2166 even introduced extern crate foo as _; to avoid bringing crate's name into scope/module for linkage-only crates.

@est31 Bikeshedding on the syntax for link_crates, instead of having it as an attribute, why don't we do something like extern crate foo;?

The whole point is to get rid of the extern crate syntax in the long run, though.

@alexreg The point is to make it unneeded in the common case. That doesn’t mean it needs to be entirely eradicated, maybe it’s still a fine solution for the edge case of a crate that needs to be linked without any Rust items from it being used. (And anyway it will live on as long as the compiler supports older epochs.)

@SimonSapin But moving forward, if it's just going to be an edge case, perhaps having special syntax for it doesn't make sense, and an attribute (as proposed above) would make more sense.

I still think use foo or use foo as _ should be enough here, really

Agree with @Manishearth.

Summarizing my view on extern crate;:

Advantages:

  • extern crate gives you a comprehensive list of used crates right at the top of your library. Now with normal crates this isn't that relevant as there is Cargo.toml, but if you have examples this is highly useful as it gives you a list of crates to import. Just think, what if your examples just import a subset of your dev-dependencies? This has high benefits when trying to figure out the API of a new crate, aka learnability benefits. On the other hand you can mention that extern crate may appear anywhere in the crate so the list does not have to be exhaustive, but for examples this is less relevant. We can always change extern crate to only work in the crate root if we want to.

Disadvantages:

  • extern crate is a minor annoyance as it means more stuff to type when adding a crate. I think this is the reason for why many people are against extern crate but for me it is not the motivating reason.
  • I see extern crate to be part of a trade: we give up extern crate but gain the distinguishability between import-from-own-crate vs import-from-exernal-crate. This is a far more useful feature and outweighs IMO the learnability disadvantages with code examples/tests. This is the motivating reason for why I agree with the removal of extern crate.

extern crate gives you a comprehensive list of used crates right at the top of your library. Now with normal crates this isn't that relevant as there is Cargo.toml, but if you have examples this is highly useful as it gives you a list of crates to import.

I don't see how this is relevant to the discussion here; this is a point against an already-merged RFC.

@Manishearth I was more replying to @retep998 here who kind of suggested to keep extern crate. And no, a point is not illegitimate or irrelevant just because it is against an already merged RFC. One needs to reflect and know the advantages and disadvantages of descisions. We can still e.g. remove extern crate from lib.rs but keep it for hello_world.rs for the ergonomics and learnability benefits (for the users of the library, not its authors).

It's irrelevant because the thing being discussed (in Peter's comment that you reply to) is link-only crates; which are a niche enough use case that "list all your extern crates at the top" is a marginal benefit. Especially if you have dependencies that are _not_ link-only crates as well (This used to not be true for Firefox, but it is now).

If your point is a more general one about having the implicit extern crate thing not apply to tests (and not specifically about link-only crates), that's an interesting point, but I don't really agree with it because it's going to be more confusing when it's used in only half the code.

So, if I'm not mistaken, the only remaining implementation point here is #48719. Is anyone tackling that now? If not, I can have a go, I suppose...

Yes, we're trying stuff out in #50260

On Sat, Apr 28, 2018, 9:15 AM Alexander Regueiro notifications@github.com
wrote:

So, if I'm not mistaken, the only remaining implementation point here is

48719 https://github.com/rust-lang/rust/issues/48719. Is anyone

tackling that now? If not, I can have a go, I suppose...


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/rust-lang/rust/issues/44660#issuecomment-385187379,
or mute the thread
https://github.com/notifications/unsubscribe-auth/ABivSMyg6L4nQ1O7nMcvY4JCGjWKRiq3ks5ttJWhgaJpZM4PaPWi
.

48722 is done. @Manishearth could you update the OP, please :)

@mark-i-m @Manishearth https://github.com/rust-lang/cargo/issues/1311 is also completed, so maybe it's worth changing that point into a checkbox and ticking it. I'm curious though: what's the approach when Cargo isn't used, going forwards? (Not that anyone sane eschews Cargo...)

@alexreg The mechanism Cargo uses to tell rustc to rename a crate is equally accessible to people not using cargo: --extern name_rustc_sees=path_to_dep.rlib.

Looks like we'll have finished the entire implementation stage soon – when https://github.com/rust-lang/rust/pull/47992 lands, in fact. Am I missing anything? Happy to help move this along if there are any bits and bobs remaining to do.

Yeah, it's on its way. I need to wrap that up which I'll likely do early
next week.

On Thu, May 3, 2018, 7:51 PM Alexander Regueiro notifications@github.com
wrote:

Looks like we'll have finished the entire implementation stage soon – when

47992 https://github.com/rust-lang/rust/pull/47992 lands, in fact. Am

I missing anything? Happy to help move this along if there are any bits and
bobs remaining to do.


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/rust-lang/rust/issues/44660#issuecomment-386494018,
or mute the thread
https://github.com/notifications/unsubscribe-auth/ABivSDYFonDWvxZEdxWXoykroaL2mJPxks5tu8I_gaJpZM4PaPWi
.

@Manishearth Good stuff.

@aturon with https://github.com/rust-lang/rust/pull/50260 landed, is everything implemented?

Anybody got the link to the plan that was proposed after the RFC? The one that more closely resembles what was actually implemented? (where imports of --extern crates are added to the prelude, and etc.)

Since this is such a significant change, maybe the RFC should be updated?

RFCs are generally not updated; they're not a final spec. They're a
consensus building tool.

On Mon, Jun 18, 2018 at 9:43 PM, Who? Me?! notifications@github.com wrote:

Since this is such a significant change, maybe the RFC should be updated?


You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
https://github.com/rust-lang/rust/issues/44660#issuecomment-398247665,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AABsij-Iwwb7vf4qBrsq9KFFqhuIbkVBks5t-Fc3gaJpZM4PaPWi
.

@mark-i-m That discourse post stays inside the spirit of the RFC. Compare the summary of the RFC with that post, it's mostly the same, beyond little details. There were RFCs where the team decided to do a totally different change, but this is not one of them.

That's all true, but it would be nice for it all to be in one place. I do understand the logistical challenge of that though...

🔔 🔔 Note that an early 2018 Edition Preview is now ready, and includes the module system changes. Please take a look and leave feedback here!

@aturon The last update, and the last meeting of the module working group, resulted in https://internals.rust-lang.org/t/the-great-module-adventure-continues/6678/205 , which specifically said "narrowed down to a single core proposal, with two variants". To the best of my knowledge, that was still true. I had the impression that nightly had implemented both, via feature flags, to support experimentation with both.

It seems that the edition picked one of the two variants, chose that to support without feature flags, and documented it in the edition guide. Where was that discussed? Because as far as I can tell, neither the module working group nor the lang team were involved there.

Please see https://internals.rust-lang.org/t/relative-paths-in-rust-2018/7883 for a new proposal, building from the 2018 preview. In particular, this proposal provides more consistency and uniformity between the root module and submodules, and uses the same name resolution both in use statements and in direct uses of paths in code.

Hi, just my 2cents on the ::some::path vs crate::some::path - I prefer the latter, mainly because it's easier to read as plain english and doesn't leave crazy left-hanging punctuation.

Please take a look and leave feedback here!

I hope this is what you meant. After reviewing a large change to the futures crate, in which all pub(crate)s were changed to crate, I'm more convinced that using crate both in imports and as a visibility modifier feels unnatural and confusing.

crate enum Either<T, U> {
    A(T),
    B(U),
}

I find it especially confusing when the two are used together:

crate use crate::foo;

As I mentioned in the lint issue to try to get us all to switch over, that would make me very sad, and I'd probably want a 3rd party lint implemented to do the opposite, to be used in libraries I maintain: stopping people from converting pub(crate) to just crate.

(Note: I'm not commenting about any other part of the changes, which seem good for consistency, only the change from pub(crate) to plain crate.)

@seanmonstar With the proposed additional changes to the module system in the next edition preview, I'm expecting that crate::foo will become rarer, and crate use crate:: seems like an especially rare occurrence.

With the module system in the initial 2018 preview, you'd need to write crate:: even for references deeper into your crate. With the proposed module system, you'd only need crate:: for references up in your crate (for instance, from foo.rs to lib.rs, or from foo/bar.rs to foo.rs), but not for references down in your crate (for instance, from lib.rs to foo.rs, or from foo.rs to foo/bar.rs). And generally, I'd expect re-exports (whether via pub use or crate use) to re-export something from a deeper module in a higher-level module, not the other way around.

So in general I'd be surprised to see crate use crate::foo;.

And yet, in a project I'm working on, I have exactly this scenario. I have a module that contains several "generic" helpers, and then another module defining a base trait + combinators, and since they make use of a couple of the generic helpers, I have re-export to help myself out in other parts of the crate:

Currently:

// in filter/mod.rs
pub(crate) use ::generic::{Combine, Func};

// in filters/foo.rs
use ::filter::{Filter, Func};

The lint will want me to change this to:

// in filter/mod.rs
crate use crate::generic::{Combine, Func};

So, I wasn't trying to come up with a hypothetical no one will ever encounter. It's real.

I fully agree with @seanmonstar. Let's not overload this keyword unless we have to (which we don't).

Yeah, you're right that reexports _usually_ bubble up, but being reexported _laterally_ is also a thing, as Sean outlines. It's something I'm considering doing for the servo-media crate as well, since the files are split up pretty nicely but that just means a lot of common imports in all of our node implementations.

Will it be possible to continue declaring what is used in a module and possibly require explicit use, maybe as a crate attribute? I prefer to be able to know by looking at a source file what is in scope within it. Having a couple lines of root-level imports at the beginning of a source file helps give some context for what is actually going on inside of it, rather than just having all external dependencies implicitly in scope, and helps greatly when refactoring.

Having tried the conversion on one of my crates, I wanted to mention like some others I've seen talking about this, I'm not convinced using crate as a visibility modifier instead of pub(crate) is an improvement. My reasoning here is that the crate moniker is quite widely used, and just sticking it in front of items makes it a little messy (it reminds me of the keyword soup you get in some languages, like Java's public static void main() -- at least Rust has the return type on the end). pub(crate) at least retains the pub to make it more obvious that crate deals with visibility in this context.

Here's another instance I just ran into that combines the crate keyword in both a visibility modifier and a path, that seems even more likely than the crate use crate... instance.

Current:

pub(crate) struct Bar {
    pub(crate) foo: ::Foo,
}

pub(crate) struct Baz(pub(crate) ::Foo);

With changes from this tracking issue:

crate struct Bar {
    crate foo: crate::Foo,
}

crate struct Baz(crate crate::Foo);

I personally find the new one to be more confusing than the current way.

When I first read the RFC to convert pub(crate) to crate I thought it sounded like a no-brainer. pub(crate) looks very odd compared the rest of the related Rust syntax in this area.

But...

After converting a little Piston game to Rust 2018 and seeing it first hand, I must admit I'm sympathetic to @seanmonstar's and others comment that having a bare crate sprinkled everywhere was cognitively jarring. I can't be sure whether I would naturally get used to this over time or not.

As other's have said, it just feels like the keyword means something very different in both contexts.

use crate::menu::{Sound, Volume};

crate mod color;

...

/// A type for storing text and an associated color it should
/// be drawn as.
crate struct ColoredText {
    crate color: types::Color,
    crate text: &'static str,
}

I want to be clear I do really like the rest of this proposal (the module system updates).

I'm sympathetic that pub(crate) seems a little heavy-handed and also odd, but also a bare crate feels a little out of place.

Could we just consider a different keyword here instead of reusing crate?

@neon64 Suggested internal which sounds reasonable, but there could be others. Since we can add keywords in Rust 2018, we have the opportunity now to consider this.

internal seems heavier than pub(crate) to me. It's not any shorter and it's a new keyword.

How about int? :P

Inside (ins), internal (int), protected (pro), local (loc), secret (sec), inner (inn) there is plenty of alternatives to the crate keyword.

Will int be confusing to C/C++ developers though? (Integer keyword)

as a programmer, when you see pub you treat it as public, if you want to introduce another visibility level then you need it to be adjective to stay consistent. As far as I remember pub stood for publish initially and in both cases pub(crate) feels more natural. Crate is just odd keyword for that.
I think it's better to leave pub(crate) as is or add abbreviation from adjective.

also I think difficulty with naming goes from the fact that pub is abbreviation, if it was public I think it would not be a problem to call it private or internal and forget about it.

Personally I just don't see the problem with pub(crate). The syntax is clear and unambiguous at first sight and consistent with actual Rust. I don't think you have to type this often enough that 5 extra key strokes are a problem.

@UtherII

The reason why we need a short name for pub(crate) is because a lot of (maybe even most of) the current uses of pub will be replaced with it.

In Rust 2018, using pub for effectively private items (because they appear in in private modules) will be a warning. One should use pub(crate) for such items instead. This convention will improve readability of code: an item is visible to other crates if and only if it is marked pub, while currently whether it is visible to other crates may not be obvious at a glance.

Sorry, I accidentally created a new issue for this rather than responding to this thread. Oops, I am rather new to github and simply clicked a button that caused me to sign in to what I thought was responding here but which was actually creating a new issue. I will paste what I wrote in the new issue here, and my new issue can be deleted, sorry about that.

Seeing as the edition manual suggested people leave feedback here, I've decided to do so.

I don't really mind either crate:: or :: for accessing elements from the crate root. :: is what is already the syntax however, so if it is possible to have both crate:: and :: I think that both should be used. The way I see it is that the new suggested crate system regarding paths is essentially equivalent to the old syntax, with the only difference being that you no longer need to use the extern keyword, and things are more accessible rather than needing to use them explicitly in submodules they are essentially implicitly imported everywhere.

The only other addition seems to be that you start the crate root with crate:: rather than ::. I prefer starting it with just ::, as this is consistent with the way I originally learned it, however I can see people who haven't learned the module system yet finding starting it with crate:: more intuitive. Why not allow for both syntax forms? Is there some reason that makes it infeasible to have both of them? If both can be supported I thoroughly endorse supporting both of them. If only one can be supported, I am inclined more so toward :: just as it is what I've become accustomed to, though it may be easier for newbies to learn it as crate:: and I can trivially update my understanding of it to be such, so perhaps my motivation is selfish.

So essentially, I ultimately don't really care, but I personally prefer ::, but I think supporting both would be ideal, though if one must be selected and people think that starting it with crate:: is superior for newbies then I don't object to this.

How is #[path] supposed to work with nested modules in Rust 2018 ? (https://github.com/rust-lang/rust/issues/35016#issuecomment-409185342)

IIUC, right now, given two files src/lib.rs and src/bar.rs, there is no X that can be substituted in:

mod foo {
    #[path = "X/bar.rs"]
    mod bar;
}

such that the module bar.rs will be found, because the path to bar.rs will always be src/foo/X/bar.rs which is invalid because the directory foo does not exist.

Since we're bikeshedding on a good name for a pub(crate) replacement and the core team is still seeking feedback, I would like to share my experience and offer a suggestion.

In one crate I wrote, I found typing pub(crate) everywhere infuriating. So I just shortened it to pub and inadvertently exported a lot of symbols that didn't need to be visible outside of the crate. Oops. 😛 (In my specific case it's not really a problem, because the crate is internal and unpublished, but still! Convenience over correctness.) So yeah, I strongly believe we need something better than a mashup of two keywords to convey crate-level visibility.

But I don't believe crate is the right keyword for it (see above for feedback from others on this topic), and I feel like some other suggested keywords kind of miss the point by approaching the problem from the perspective of the crate root; the issue lies at the perspective of the module, i.e. the specific line of code containing the keyword. In other words, the code is not trying to provide "crate locality", it's trying to export the reference from the module where it is defined.

In that sense, I like the keyword export (even if it's possible to confuse with extern, but maybe that's moot since extern crate is dead?) export struct Foo; looks very readable, and aligns with some other languages (ish). I wasn't able to find any mention of export as a keyword, in this RFC or elsewhere. So there's also that going for it.

For completeness, it covers the use cases brought up by @seanmonstar, @johnthagen, et al.:

export use crate::generic::{Combine, Func};

// Or even better using relative paths
export use ::generic::{Combine, Func};
export struct Bar {
    export foo: crate::Foo,
}

export struct Baz(export crate::Foo);
use crate::menu::{Sound, Volume};

export mod color;

// ...

/// A type for storing text and an associated color it should
/// be drawn as.
export struct ColoredText {
    export color: types::Color,
    export text: &'static str,
}

All that said, I'm in favor of killing pub(crate) with fire. I even prefer crate, FWIW.

@parasyte The problem I see with export is that it could be confused with "this crate exports ____" (which is what pub is for), but I see how you're advocating for a different perspective.

It seems like most people agree pub(crate) isn't ideal, and many have raised concerns that the noun keyword crate, which now used in other contexts, can be jarring as a replacement. Making sure we've fully considered other (potentially new) keywords seems like it would be a very good use of time before Rust 2018 sets this in stone.

I haven't heard any "official" feedback if this is still open for discussion though?

In the interest of introducing some more words along the lines of @parasyte's suggestion (I agree with @johnthagen that export seems more like pub than pub(crate)):

shared use ::generic::{Combine, Func};
shared struct ColoredText {
    export color: types::Color,
    export text: &'static str,
}
global use ::generic::{Combine, Func};
global struct ColoredText {
    export color: types::Color,
    export text: &'static str,
}

We could also follow in Java's footsteps and use something like protected, but I'm not sure that that's particularly easy to intuit the understanding of. There's also local (in the sense of crate-local), but I think global is actually less likely to confuse (e.g., local could be _file_-local).

How would people feel about pub(cr) or even just cr?

Using crate seems too good to pass up on. It is already a keyword, its previous use in extern crate is going away. Its other use is already related to visibility pub(crate).

If you squint a little using crate as an adjective doesn't sound that disconcerting. Words such as 'house' can be used as adjectives just fine. IANAEnglishProfessor.

I think reexporting a crate visible item isn't super problematic. Personally I've only ever reexported an item in a direct parent of the module which defines it, which means you (probably) want to use self instead of crate (seanmonstar's case of code organizing notwithstanding). Eg.

    mod detail {
        crate struct Foo;
    }

    crate use self::detail::Foo;

Using crate as a visibility combined with absolute path to a type (again using seanmonstar's example) does look more problematic: crate struct Foo(crate crate::Bar); as opposed to pub(crate) struct Foo(pub(crate) ::Foo);. My only hope is that this construct isn't popular and thus transitioning won't create this crate soup. Users can avoid this by explicit importing:

use crate::Bar;

crate struct Foo(crate Bar);

I do like the suggestion of share or something like it. give, provide, deliver, offer, serve, post, forward ... If it must be an adjective, these can all be transformed with a suffix: shared, givable, providable, etc.

To pick on JavaScript for a moment, ES6 doesn't even have a concept of package versus module built into the language. There are only modules. The export keyword in ES6 always exports the reference from the module; it's then up to another module to import that reference by path if it wants to use it.

This isn't greatly different from pub and use, respectively. and I guess that's where some of the confusion would come from with using an export keyword. pub(crate) is actually kind of niche. Which is why I opted to just use pub in the past. ;(

The issue of the crate visibility modifier has been extracted out to #53120. Further debate and decisions should continue over there.

The issue of the mod.rs changes has been extracted out to #53125. Further debate and decisions should continue over there.

The issue of the extern crate deprecation as well as having use crate_name::foo and crate_name::foo Just Work™ has been extracted out to https://github.com/rust-lang/rust/issues/53128. Further debate and decisions should continue over there.

The issue of picking a Module Path System has been extracted out to https://github.com/rust-lang/rust/issues/53130.

Having extracted out all bits from this issue into separate issues; I am hereby closing this one.

@Centril: https://doc.rust-lang.org/unstable-book/print.html#extern_prelude links here. Where is this being discussed? If this info is indeed missing, could you add it to the OP?

@sanmai-NL can you refresh my memory on what the "extern prelude" was?

If it is just the ability to do use some_crate::foo::bar; then that should be https://github.com/rust-lang/rust/issues/53128.

Some of it is being discussed in https://github.com/rust-lang/rust/issues/54230.

@sanmai-NL I believe that issue is mainly for diagnostics.

extern_prelude is crate_name::foo::bar outside of use (import) paths.

@Centril

can you refresh my memory on what the "extern prelude" was?

In general, "prelude" is currently used for all names that are in scope for the whole crate and not attached to a specific module. (There are a lot of those actually.)

@petrochenkov So what's "extern" prelude in relation to that?

@alexreg
Crates passed with --extern are in scope for the whole crate while not being attached to any specific module.

It would be great if these nuggets of info, no matter how volatile, are recorded in some official source of documentation. Esp. if there are outside mentions of the concept, e.g. in the Unstable book. I’m not saying the maintainers who implement these concepts should do that, though.

@petrochenkov Thanks, makes sense.

Having extracted out all bits from this issue into separate issues; I am hereby closing this one.

@Centril Tracking issues in the current beta version link here. Would you update the original comment with the most recent information so that people don't have to spelunk the comments?

Was this page helpful?
0 / 5 - 0 ratings