Tracking issue for rust-lang/rfcs#1422
Milestones:
You probably meant rust-lang/rfcs#1422
@jonas-schievink indeed. :)
How to parse struct S(pub (std::marker::Send + std::marker::Sync));
? It's currently legal.
If we try to parse std::marker::Send
as a part of pub(restricted)
based on is_path_start(std) == true
, then we end up in the middle of a type sum after parsing an arbitrary number of tokens with only a Path
in hands and need to recover.
Similar cases with simpler disambiguation:
struct Z(pub (u8, u8));
struct W(pub (u8));
Note that pub(path)
is a strict subset of pub TYPE
(modulo pub(crate)
), so the former can be parsed as the latter and reinterpreted later if necessary.
I suppose everything here can be disambiguated, but I'm concerned about the conflict pub(path)
vs pub TYPE
in general and how it affects formal properties of Rust grammar.
@petrochenkov sigh. a very good point. @pnkfelix and I were chatting-- one obvious (but not particularly appealing) option would be to say that tuple structs just can't use pub(restricted)
paths. This would be better if we had the notion that one could write struct Z { pub 0: (u8, u8) }
to use the more explicit name.
We considered various other synaxes one could use:
pub{path}
pub(in path)
pub in path
One consideration is that I expect most people will want to either make fields pub
-- meaning, as public as the struct itself -- or private. So perhaps it's ok that you can't use tuple structs to express more flexible things -- they're kind of a convenience shorthand as is. But it is unfortunate however you slice it.
For reference, @nikomatsakis is referring to #1506.
On Sun, Apr 3, 2016 at 5:52 AM, Vadim Petrochenkov <[email protected]
wrote:
Note that pub(path) is a strict subset of pub TYPE (modulo pub(crate)),
so the former can be parsed as the latter and reinterpreted later if
necessary.Hmm, I suppose that's true. (Or might be true)
So in the @rust-lang/lang meeting we discussed this problem, as well as the solution implemented in #33100. Our conclusion was that we really ought to explore the cover grammar approach described earlier by @petrochenkov here:
Note that pub(path) is a strict subset of pub TYPE (modulo pub(crate)), so the former can be parsed as the latter and reinterpreted later if necessary.
It is basically the only approach that lets restricted paths and tuple structs be fully integrated, without any artificial restrictions (like needing to convert to {}
form, or needing to introduce an extra set of parens). It's a shame that the most natural grammar would no longer be LL(k) (nor LR(k)), but given that such an obvious cover grammar exists, that shouldn't cause a lot of problems in practice.
The ambiguous syntax has come up some more on https://github.com/rust-lang/rfcs/pull/1575, where it interferes with the idea of having a "visibility matcher". Some proposals were made for alternative syntax options there:
pub{path}
pub@path
pub(in path)
Most of these were covered (and rejected) in my prior comment, but it is true that the lookahead requirement is onerous.
We could always just use another keyword (scope
, scoped
, restrict
, restricted
, vis
, etc...):
restricted(path)
restricted path
And all related variants...
What would it take to change the syntax? Another RFC or just a PR?
@durka another RFC, although it could be a modification RFC that modifies the existing one (probably with some new motivation) rather than a completely fresh RFC
I posted on users.rust-lang with some confusion about this. The RFC talks a lot about preventing items from being reexported outside their scope, but so far as I can tell in the current implementation, pub(restricted) use
_doesn't reexport anything, period_; only bare pub use
does.
My users.rust-lang example boiled down to its essence: (playpen)
#![feature(pub_restricted)]
fn main() { }
mod far {
mod outer {
pub use far::outer::space::hello_world;
pub(crate) use far::outer::space::hello_crate;
pub(super) use far::outer::space::hello_far;
pub(self) use far::outer::space::hello_outer;
mod space {
pub fn hello_world() {}
pub(crate) fn hello_crate() {}
pub(far) fn hello_far() {}
pub(super) fn hello_outer() {}
}
mod bar {
use super::hello_world; // ok
use super::hello_crate; // ERROR: no `hello_crate` in `far::outer`
use super::hello_far; // ERROR: no `hello_far` in `far::outer`
use super::hello_outer; // ERROR: no `hello_outer` in `far::outer`
}
}
}
Is this working as intended? To me, it seems that pub(crate) use
, pub(super) use
, and more generally pub(some::path) use
all currently have the same meaning---which feels odd.
@ExpHP
You have to add #![feature(item_like_imports)]
as well.
Ah, I see. I notice said feature also reexports unmarked use sub::module::item
(consistent with the RFC's statements that _no pub is the same as pub(self)_), so I see now how it could possibly be a contentious feature...
(...not that it bothers me! I rather wish it were that way all along! :stuck_out_tongue:)
Edit: Silly me, this has nothing to do with the feature being contentious; it's actually just this RFC
Would (pub restricted)
be unambiguous?
Some proposals were made for alternative syntax options there
After further discussion with @nikomatsakis, we are wondering whether pub{restricted}
(instead of pub(restricted)
) is all that bad...
It seems like the shortest path to something that would remain as palatable as the status quo while fixing the struct Tuple(pub (TYPE))
problem.
I am in favor of pub{restricted}
. Well, mostly I am in favor of stabilizing this syntax form -- and that seems the most straightforward and uncomplicated syntax for achieving it. =)
cc @rust-lang/lang -- what does everyone think about the alternative syntax pub{restricted}
(note: braces) instead of pub(restricted)
?
I like the prospect of potentially expanding the feature to sibling ("friend") modules, which would match multiple import use
syntax if we have braces, so for that reason I'd prefer them over parens.
If pub{restricted}
can help stabilize this, then ok. I'd like to see short cuts for the most common patterns, like crate fn
to mean pub{crate} fn
. I feel like most people won't bother with finer grained restrictions and use pub{crate/super} most of the time, at least I would. Perhaps a contextual keyword could be used, like in default's case.
I've probably been spending too much time on rustfmt, but pub{foo}
looks very bad to me - I think it is rare to use braces like this? We mostly use them for block-like things, the only exception being in use
expressions, where they are always preceded by ::
(and where, thinking about, I'd prefer to use parens anyway). I'd prefer using []
or <>
instead of {}
- I don't think any is really appropriate, but at least they suggest block formatting less.
The {}
braces as found in use
are similar to Bash brace expansion, where you can read it as repeating the prefix for each item in the list. (use foo::{bar,baz};
-> use foo::bar; use foo::baz;
) That repetition doesn't exactly fit the pub
idea as well, especially since the braces aren't optional with a single visibility spec.
I like pub[restricted,crate,etc]
a little better, because it looks more like an attribute list. Or maybe it should be an actual attribute? I only found that briefly considered in https://github.com/rust-lang/rfcs/pull/1422#issuecomment-169582057.
I spent some time this morning revisiting the initial RFC thread and reading over this one. Like others on the lang team, I'm eager to make a decision about the syntax and get this stabilized. (I personally don't think a new RFC is required to do this; we frequently make surface adjustments like this during the stabilization process.)
A couple of thoughts:
In the original thread, there was a fair amount of discussion about how much ground would be covered with pub(crate)
and pub(super)
. We could reconsider the ability to write arbitrary paths. If we were limited to these keywords, I think the syntax would be fine. @pnkfelix, you were pretty against such a limitation if it wasn't forward-compatible with arbitrary paths. What are your thoughts now?
I personally find pub{crate} fn foo(arg: Arg) -> Res
quite jarring. It's weird to have it show up in the middle of an already syntactically-complex form, it currently accepts only a single item, and it has little precedent, AFAIK, in curly-brace-style syntaxes. I think we'd catch a lot of flack for it.
pub[crate]
seems better, especially with the analogy to attributes; I might actually prefer it to today's syntax. I don't think it should actually be an attribute, though; it's a modification to the meaning of the pub
keyword, and I think should be part of the syntax proper.Is there a strong argument against the pub in crate fn foo(arg: Arg) -> Res
syntax? It does look pretty verbose and difficult for the eye to parse...
I don't think pub[m]
works any better, as [T]
is a valid type and IIRC the problem was with tuple
structs which have a visibility just before each field type.
I like the idea of simplifying it to pub[crate]
and pub[super]
. But if those are the only options, then is the square bracket notation needed at all? I think keywords could be better. The simplest way would be to add a crate
and a super
visibility modifier:
fn do_something() {
// ...
}
pub fn do_something() {
// ...
}
crate fn do_something() {
// ...
}
super fn do_something() {
// ...
}
@eddyb ah yes, of course you're right. Drat.
I advocated for limiting this feature to crate
and super
during the RFC and still think that's a pretty reasonable approach. Since the RFC I have still never wanted anything else, but I am usually in crates of less than 10k lines of code.
I am still not wild about supporting only crate
and super
. I agree those will be the most frequent cases, but I definitely find that when I am writing code, I often have top-level modules that form "mini-crates" of their own:
mod ui {...}
mod solve {...}
Most of the time, those top-level modules have only a single level, and hence when importing from cousins I will use super
. This would be a good fit:
mod ui {
mod button {
use super::form::Form;
}
mod form {
...
}
}
But I often find that at least one such module gets complex enough to warrant some internal structure. I personally find super::super
pretty hard for me to think about and hence at that point I tend to switch to absolute paths:
mod ui {
mod button {
mod draw {
use ui::form::Form;
}
mod other { ... }
}
mod form {
...
}
}
I imagine this same pattern will repeat, but I will almost certainly not want to write pub(super::super)
, because that is very long. Writing pub(ui)
would be nice. But in practice I guess I would either be forced to keep module hierarchies shallower than I might have otherwise done, or else to use pub(crate)
.
In a way, I feel like I might be happier with just pub(crate)
than with pub(crate)
and pub(super)
, since at least then the limits are clear up front, versus being something that you only encounter when you try to break-up existing code into a more hierarchical structure. =)
EDIT: And yes then I would favor just writing crate
and not pub(crate)
.
I think what I do in those situations is that I essentially repeat the "subcrate" pattern fractally, so that each mod file is a flat facade of its children which it presents to its siblings & parent.
I also would probably be happy with just using crate
to mean pub(crate)
and no other fanciness in this feature. But again, I'm not dealing with as large crates as other users.
Coming from a C# background (and I believe it's the same in Java) having just pub(crate)
makes a lot of sense, it's equivalent to internal
visibility. I really can't think of any time where I have wanted to be able to limit visibility more than that, again though I rarely work on high LOC projects (and the ones I have worked on just had everything public and relied on code reviews + decent developers to not make a mess).
If this is stabilized only allowing pub(crate)
and pub(super)
, does that preclude allowing arbitrary paths in the future? Maybe it would make sense to stabilize a more conservative part of this feature set that everyone is desired and future-compatible (like conservative impl trait), since it sounds like there is still concern from many people that allowing arbitrary paths might not be useful enough to warrant its baggage.
I think what I do in those situations is that I essentially repeat the "subcrate" pattern fractally, so that each mod file is a flat facade of its children which it presents to its siblings & parent.
That doesn't really work. For example, consider this struct Draw
declared inside ui::buttom::draw
. I want Draw
to be exposed to all of the ui
module. What pub should I use when I declare it?
mod ui {
mod button {
mod draw {
pub(X) struct Draw; // <-- what to write here for X?
}
mod other { ... }
pub(super) use self::draw::Draw; // <-- `Draw` is exposed to `ui` as `button::Draw`
}
mod form {
...
}
}
If I use pub
, it is broader than I wanted. Same with pub(crate)
. But pub(super)
is incorrect: that would make it private to ui::button
, and I wanted ui
. pub(super::super)
would work, but that is the thing I am saying I find unwieldy. I would rather write pub(ui)
.
But I realize that I can't have what I want if we want to solve the parsing ambiguity (unless we adopt pub{ui}
, and I agree it's kind of ugly). Maybe it's worth it to just have the keyword paths like pub(crate)
and pub(super)
-- but I think we should probably add pub(self)
too, in that case, as it is gives us a mechanism to add "private" declarations in some places where we currently lack that ability. =)
@nikomatsakis It seems like your concerned about preventing other pub use
statements from re-exporting Draw
further afield than you'd like. I guess my answer is that I am just not very concerned about that.
Without any restrictions (just raw pub
) your example is already exposing Draw
only to ui
. The concern seems to be that someone in ui
does pub use self::button::Draw;
when they shouldn't have. Is that what you see this feature mainly as preventing?
If it is, to me it seems more likely that someone will do that because they've decided they need to use Draw
somewhere outside of ui
, and so they remove your restrictions. That is, these roadblocks don't seem likely to stop someone from doing what you fear because they would only do that if they feel exposing it is well-motivated and that your decision to restrict it was wrong.
The point of this feature seemed to me to be more about convenience - if I'm not feeling too attentive to visibility but I know I don't want it in my public API I can just use a crate
visibility and call it a day.
Could a different keyword solve the ambiguity? For instance, pub
stays as it is currently, and a vis
keyword is added, which always contains a path/modifier.
pub fn()
vis(pub) fn()
vis(self) fn()
vis(crate) fn()
vis(some::path) fn()
Also maybe relevant, @withoutboats post on the Rust module system being too confusing (forum thread). If users are finding the module system itself too confusing, what are they going to think about a visibility system built on top of the module system.
It seems like your concerned about preventing other pub use statements from re-exporting Draw further afield than you'd like. I guess my answer is that I am just not very concerned about that.
Fair enough. =) It is indeed the model I was shooting for.
So, let's posit that we are going to simplify to super/crate/self
. If all we're looking for is a convinence that lets you be sure you don't contaminate your public API, is it worth considering just crate fn foo()
as an alternate privacy level (public-crate-private), basically. Like a better version of Java's "package", I guess.
Pro: Short and sweet.
Con: pub(super)
is also a fairly common desire.
Con: Makes crate-level special, which is sometimes seen as a suspicious sign.
Somewhat unrelated, but I like the idea of writing crate impl Foo
as a way to add crate-local inherent methods (which could then be added to types outside your control). I want this all the time. (Though it also raises the question of whether one could have crate-private impls of traits, which raises yet more thorny issues.)
It would also mean that (in specialization) we couldn't have default(crate)
, or at least it wouldn't have the natural correspondence to pub(crate)
.
I'm not seeing pub in(path)
mentioned as an alternative. It has the beautiful property that it requires no lookahead inside a paren to distinguish it from anything else (assuming we're fine with never having types that start with in
) and it's a bit more self-descriptive than everything else.
And if we want the crate
shorthand we could stabilize that first (but, uhm, crate extern crate
would parse, wouldn't it?) and try to make sue pub in(path)
isn't stabilized without tested macro interactions.
@eddyb I considered it at some point I think, but it seems kind of long to me, and I find the space
a bit hard to parse (hard to see that the in
is associated with the pub
).
pub struct Foo {
pub in(ui) f: Bar
}
vs
pub struct Foo {
pub(ui) f: Bar
}
Just for reference sake, here it is with crate
:
pub struct Foo {
pub in(crate) f: Bar
}
vs
pub struct Foo {
pub(crate) f: Bar
}
vs
pub struct Foo {
crate f: Bar
}
And ... yeah ... crate extern crate
would parse. All the more reason to deprecate extern crate
, but that's another discussion, isn't it. :)
@nikomatsakis I think another con for just crate
is that it has no obvious relation to visibility (although maybe if it were the only reason to use crate
, that would be different).
It would also mean that (in specialization) we couldn't have default(crate), or at least it wouldn't have the natural correspondence to pub(crate).
I think it is worth thinking of a solution to both these problems at the same time - seems that visibility and specialisability (and maybe overridability one day, depending on where we go with inheritance) are all properties that should be defined in the same way.
re pub in(foo)
I also dislike the space, but it would work for default
and reads kind of nicely. I think I prefer pub(foo)
/pub[foo]
/pub<foo>
more than either, but pub in(foo)
better than crate
.
Would it be horrible to use @
for this? This could be with some sort of optional bracketing, only required with multiple paths or for disambiguation.
pub@crate
pub@[foo, bar]
struct Foo(pub@foo Bar)
struct Foo(pub@foo::bar Baz) // questionable, requires lookahead on ::
struct Foo(pub@[foo::bar] ::Baz) // brackets necessary for disambiguation
@withoutboats ah, I remember now another use-case for this feature. Often I have a type that IS publicly exposed to the world, but where I would like to expose some of its fields to some other modules. So in my ui
example that might be like:
mod ui {
mod button {
mod defn {
pub struct Button {
pub(ui) id: u32 // ui widgets get to play with this field, but not others
}
}
pub use self::defn::Button;
}
pub use self::button::Button;
}
@cuviper I think that is ambiguous too. Consider struct Foo(pub@foo::bar::baz)
-- is that struct Foo(pub@foo (::bar::baz))
? Or struct Foo(pub@foo::bar (::baz))
?
@withoutboats wrote:
That is, these roadblocks don't seem likely to stop someone from doing what you fear because they would only do that if they feel exposing it is well-motivated and that your decision to restrict it was wrong.
Its fine with me if someone decides that I was wrong and revises my definition to increase its visibility.
Why is that fine? Because someone who comes along the code later and sees pub item-defn
will say "ah, at first blush, I will assume that this item is exposes to the universe at large." (And likewise, someone who sees pub(crate) item-defn
will likewise make a similar conclusion except the exposure is to the crate at large.)
In other words, one important benefit I see of using pub(restricted)
is that someone who reads the code can quickly locally reason about how exposed the item is. (Later they might employ more global reasoning to narrow down the uses.)
In response to the side thread between @eddyb and @nikomatsakis about pub in(path)
: I too find the space between pub
and in
to be hard to visually parse.
However, if we assume that the common case here is going to be crate
and super
, perhaps we can do this: allow both pub(crate)
and pub(super)
, but for arbitrary paths, one writes pub(in path)
.
This way, the common cases remain succinct, and you need to use in
to drop into parsing a more complex path.
@pnkfelix
However, if assume that the common case here is going to be crate and super, perhaps we can do this: allow both pub(crate) and pub(super), but for arbitrary paths, one writes pub(in path).
I... don't hate this.
I agree that is a reasonable use of the in
keyword and should solve the macro fragment problem.
limiting this feature to crate and super
Even if only pub(crate)
, pub(super)
and pub(self)
are left on the surface, compiler will still need to support pub(path)
internally because private items in module a::b
are desugared into pub(a::b)
and pub(super)
is desugared into pub(concrete::module)
as well.
Module-based visibilities is a really nice system that can be orthogonally applied to all kinds of access rights - sealed(path)
, default(path)
, mut(path)
and maybe something else in the future! It would be silly to restrict it today to a few hard-coded modules due to a minor parsing problem.
pub(in path)
Ugh.
Even pub{path}
looks better than this, despite looking bad enough on the absolute scale.
allow both pub(crate) and pub(super), but for arbitrary paths, one writes pub(in path)
Note that super
currently is an "arbitrary path" too, you need one more special case to make this work.
@nikomatsakis
Consider struct Foo(pub@foo::bar::baz) -- is that struct Foo(pub@foo (::bar::baz))? Or struct Foo(pub@foo::bar (::baz))?
What is the problem in disambiguating by always parsing paths greedily? (e.g. struct Foo(pub@foo::bar::baz)
is a syntactic error due to missing type)
(I'm still a fan of https://github.com/rust-lang/rust/pull/33100, btw, that is similar to greedily parsing pub@path
ideologically, but less backward compatible)
@sbstp suggests:
Could a different keyword solve the ambiguity? For instance,
pub
stays as it is currently, and avis
keyword is added, which always contains a path/modifier.
pub fn()
crate fn() // <-- my addition, possible sugar for `vis(crate) fn()`
vis(pub) fn()
vis(self) fn()
vis(crate) fn()
vis(some::path) fn()
At least one reason to like this suggestion is that it gives a common syntactic form for all visibilities (may be useful for macros?) which is also extensible to uses of visibilities in other contexts:
default(pub) fn() // <-- this is supposedly the default, but now there's a way to write it explicitly!
default(self) fn()
default(crate) fn()
default(some::path) fn()
EDIT: on the other hand, pub(pub)
can be easily introduced as well.
An obvious (but not sure if strong) reason to dislike is a new context-sensitive identifier ("weak keyword"). I'm... not even sure it can break anything? vis(path)
is a valid type syntactically (a trait object type with type parameters in parens, like Fn(u8)
), so the "tuple struct problem" takes place, but vis
needs to be a Fn
-like trait for the code to be valid, which is unlikely (impossible?).
EDIT: vis
can't be applied fully backward compatibly to items in blocks:
fn main() {
vis(a::b::c) struct S; // <-- visibility
vis(a::b::c::d); // <-- fn call
}
Because someone who comes along the code later and sees pub item-defn will say "ah, at first blush, I will assume that this item is exposes to the universe at large." (And likewise, someone who sees pub(crate) item-defn will likewise make a similar conclusion except the exposure is to the crate at large.)
Right now I don't assume that, because without other pub mod
or pub use
declarations, it isn't true (and this feature doesn't exist right now). To me it seems like an unfortunate outcome ifits normal for every exported item to have a scoping qualifier on its declaration, because I feel that it would introduce a lot of noise.
I think of this as largely a convenience for complex facading, not something that would be used all the time.
@nikomatsakis
I think that is ambiguous too. Consider struct Foo(pub@foo::bar::baz) -- is that struct Foo(pub@foo (::bar::baz))? Or struct Foo(pub@foo::bar (::baz))?
Yes, there would need to be something to disambiguate this. It's enough just to parenthesize the type as you've done, if we don't need to support a list of visibility scopes, otherwise that will need some kind of bracketing to group it. I think eager parsing the bare path is fine, as @petrochenkov suggests, especially if the "missing type" error can suggest the disambiguation.
@petrochenkov wrote:
allow both pub(crate) and pub(super), but for arbitrary paths, one writes pub(in path)
Note that
super
currently is an "arbitrary path" too, you need one more special case to make this work.
I don't think I made myself clear.
The idea was that pub(in <path>)
is a very general form (so yes, pub(in super)
would be legal, as would be pub(in super::super)
or pub(in self)
or pub(in a::b)
, et cetera).
But the two presumably most-common cases get special in
-free sugar: pub(crate)
and pub(super)
.
But the two presumably most-common cases get special
in
-free sugar:pub(crate)
andpub(super)
.
Having said that, I actually would probably be fine with an even sweeter sugar, crate fn foo(...)
(which would desugar to pub(in crate) fn foo(...)
). (In this case, only the crate
gets a sugar, not super
. But maybe someone can talk me into the latter.)
I think @nikomatsakis and @petrochenkov have done a fine job laying out why it is useful to provide the full-featured form. Its just a question of how similar we want all the variations of the syntax to look (or, put another way, how much dissimilarity are we willing to put up with to keep the parsing simple).
@pnkfelix
But the two presumably most-common cases get special in-free sugar: pub(crate) and pub(super)
Nit: pub(super)
still has the "tuple struct" issue - pub struct S(pub (super::S, super::Z));
, but it's solvable by couple of tokens of lookahead.
I'm still not happy with pub(in path)
(see https://github.com/rust-lang/rust/issues/32409#issuecomment-270942220 for my preferred alternative), but I think I can tolerate it if the most common cases pub(super)
, pub(crate)
and pub(self)
are all allowed without in
.
I'm 👍 on either pub(super)/pub(crate)/pub(self)
or crate fn
. I think I lean towards pub(X)
and friends because it permits default(X)
as well (for constraining the scope of specialization lexically).
crate fn
looks great to me. It's simple. It's a model familiar from other languages where it works well enough.
And it encourages splitting project into crates, rather than building monoliths with holes poked for arbitrary coupling.
I'm trying to figure out how to draw this discussion to closure. I think there's a rough consensus that the following are the two most plausible options:
Support only crate fn
etc. That removes a fair bit of expressiveness, as well as the potential for applying the technique to things like default
, but it is very simple, pretty, and matches other languages.
Support the full range of functionality, using pub(crate)
, pub(super)
, pub(self)
and pub(in foo)
, where the last one is expected to be somewhat uncommon. This syntax applies cleanly to things like default
, and while a bit ugly, makes the qualification happening very clear. And of course we can add crate fn
as sugar on top, if we want.
I think most stakeholders have said they are OK with at least one of these two choices. Does anyone have strong objections to the second choice? If so, what are they? To me, it seems like it provides the best balance of clarity, expressiveness, extensibility, and tolerable syntax.
For reference (I haven't seen this mentioned in this thread):
pub(self)
is also useful for fixing issues like https://github.com/rust-lang/rust/issues/32770 without reinventing priv
.
My only concern on the second is that I'm not sure that pub(in path)
is really pulling its weight. I'm pretty in favor of pub(crate)/pub(super)/pub(self)
.
I'm not saying you'd never want it, I'm just saying maybe you don't want it as bad as you want to not have to think about the privacy scopes you can create it with.
@withoutboats Do you think that supporting all of crate
/super
/self
, plus the points about working with default
, are enough to still argue in favor of the pub(restricted)
syntax?
I'll note that we can stabilize the pure-keyword versions first, and take longer on the in
variant.
@aturon not sure how I feel about default(crate)
, but yeah I do think pub(keyword)
is probably the best way forward for now. Are you thinking about stabilizing part of this soon?
Since AFAIK the full pub(path)
is currently implemented, it seems like the action item is to go with pub(in path)
and then consider stabilizing just the pub(keyword)
part after that's done.
Can I get some opinions on pub@path
suggested by me and @cuviper ?
I still think it's the least noisy and the most nicely looking syntax from suggested in this thread, it also doesn't require special cases for crate
etc and extensible to default@path
.
Parsing ambiguity pub@path :: type
can be trivially resolved by greedy parsing, there are already few places in the syntax where this is done.
It just occurred to me.
Syntactic ambiguity exists only in tuple structs, so let's require disambiguation in
only in tuple structs (i.e. almost never).
It will be permitted in other contexts, but not required.
Pros: 99% of code working today continues working; in 99% of cases the syntax is still concise; no special cases for super
/self
; still extensible to default(path)
; there are no problems with $vis
matchers in macros.
That's not a bad idea. It's similar to how you have to write (foo,)
to disambiguate a tuple with one element from (foo)
. It's (AFAIK) the only place this syntax is used because it's the only place it's needed.
A much more popular analogy would be the disambiguating "turbofish" ::<
in paths required only in expression contexts.
(It's not currently permitted in type paths and $path
matchers, but that's an artificial restriction.)
I'd say turbofish and comma in tuples are unfortunate quirks, and shouldn't be used as a precedent for adding more syntax quirks.
pub(self)
/pub(super)
can be added now, without deciding on fate of path (and if path turns out to be needed, then it can be later decided whether it should always have the prefix or only somtimes).
@pornel It would be weird to stabilize pub(self)
as a special case, it would just be a contrived way of saying priv
. It would be more natural that pub(self)
comes as a special case of pub(path)
.
@pornel
Value paths are different from type path in other ways (e.g., the handling of an empty type parameter list), so it's not a parse-only distinction.
I don't see a problem with pub(self)
. It's actually a great description of how it works exactly (I'm surprised that "private" struct fields are accessible from outside of impl Struct
- this is different from how private class properties work in other languages).
I suppose stabilising pub(keywoard)
without paths would be a problem if it turned out that e.g. pub<path>
parses better.
pub<path>
couldn't be used because pub <T> ::A
parses right now (as pub <T>::A
) in tuple structs.
Frankly, it feels like the struct
/enum
private/public defaults were misguided and a better choice would've been to have private/public defaults based on constructor shapes, i.e. "tuple" struct
s and enum
variants would both be public-by-default, supporting no pub
syntax, and everything else private-by-default.
@petrochenkov
Can I get some opinions on
pub@path
suggested by me and @cuviper ?
I still think it's the least noisy and the most nicely looking syntax from suggested in this thread, it also doesn't require special cases for
crate
etc and extensible todefault@path
.
Parsing ambiguitypub@path :: type
can be trivially resolved by greedy parsing, there are already few places in the syntax where this is done.
I'm personally open to this option, though I have a weak preference in favor of the ()
-based syntax. In particular, I personally find it easier to visually parse:
pub(module::submodule) fn foo();
pub@module::submodule fn foo();
default(module::submodule) fn foo();
default@module::submodule fn foo();
@petrochenkov
Syntactic ambiguity exists only in tuple structs, so let's require disambiguation
in
only in tuple structs (i.e. almost never). It will be permitted in other contexts, but not required.
That seems reasonable to me. If we want to consider this route, I'd suggest first stabilizing the explicit in
syntax and seeing whether it's painful enough in practice to drop it in most locations.
I strongly don't think requiring the in
only in tuple structs is a good idea. While the language has some disambiguating quirks (trailing tuple comma and turbofish), this feels qualitatively different in a way I have difficulty explaining. It feels far more arbitrary to require an in
keyword or not depending on the shape of the struct its being used in than either of those feel.
Even though I value it, I think the use of explicit in
will be quite rare -- pub(super)
and pub(crate)
will predominate. I say we should go with pub(super)
, pub(crate)
, and pub(in path)
and live with it.
Personally, I wish that tuple struct fields inherited the privacy of the struct, but ... it's a bit late to change that. =)
@withoutboats
My general argument against solutions like "pub(in path)
everywhere" is that they are not "zero-cost".
Tiny corner case affects all the remaining language and makes it worse, even if it's never used itself.
I want a solution that doesn't suffer from this issue.
"Optimize for the common case", remember?
Even if a disambiguation quirk looks bad aesthetically when placed under the magnifying glass, it will still have zero effect in practice.
(My suggestion doesn't even look so bad, quite otherwise, it makes the grammar simpler.)
P.S. mod
may be a good alternative to in
. It gives a simple memo - if you want to disambiguate between a module and a type, use mod
to tell it's a module (kinda like typename
and template
in generic contexts in C++).
Please don't add anything that's "kinda like typename and template in
generic contexts in C++" :)
On Wed, Feb 1, 2017 at 5:23 PM, Vadim Petrochenkov <[email protected]
wrote:
My general argument against solutions like "pub(in path) everywhere" is
that they are not "zero-cost".
Tiny corner case affects all the remaining language and makes it worse,
even if it's never used itself.
I want a solution that doesn't suffer from this issue.
"Optimize for the common case", remember?
Even if a disambiguation quirk looks bad aesthetically when placed under
the magnifying glass, it will still have zero effect in practice.
(My suggestion doesn't even look so bad, quite otherwise, it makes thegrammar simpler.)
P.S. mod may be a good alternative to in. It gives a simple memo - if you
want to disambiguate between a module and a type, use mod to tell it's a
module (kinda like typename and template in generic contexts in C++).—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/rust-lang/rust/issues/32409#issuecomment-276802580,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAC3n2uj81O9nkSSSdCQtCUqBfYz7OI2ks5rYQX_gaJpZM4H1fnj
.
@petrochenkov I consider requiring in
everywhere simpler than requiring in
only in one location this feature could be used. Its not an aesthetic consideration; to be honest every argument you make is exactly what I would make in the opposite direction.
My initial reaction is I don't like mod
there since everywhere else it means to define a mod, not refer to one.
I also find pub@path
more noisy and uglier than other suggestions, despite all their problems. The @
is a nasty character.
I wish I had more constructive suggestions for the syntax.
I was thinking about the semantics of this feature in general the other day, and the fact is I would only use pub
, pub(crate)
, and the default private.
In my ideal world, pub
on an item would mean "this item is exported to other crates", i.e. there is a path to the item where every module along the path is pub
, and the compiler would error/warn if this was not the case. Then I would use pub(crate) fn
(or crate fn
) to mark those things that are pub
today but not truly accessible from outside the crate.
This would let a reader tell at a glance whether something is truly exported to external crates. It takes work to figure that out today. I don't know if we can make my ideal world happen, but I currently try to follow it as a convention in my code (on nightly where pub(crate)
is available). There's no static checking that pub
means exported, but I try to make it true manually.
Lastly, I am uninterested in making items public to a module subtree of the current crate (i.e. pub(path)
). I typically avoid deep module hierarchies where this would make sense.
In summary, I would prefer a world where we had:
fn
- private to the current module (and its submodules)pub fn
- accessible by external crates, enforced by warning/error in rustccrate fn
- accessible anywhere in the current crate that can access the containing module, like pub(crate)
Anyone agree/disagree? Maybe I've missed some compelling need for pub(path)
, but so far I don't feel like it's a big win.
I wish I had more constructive suggestions for the syntax.
Feel free to choose!
(Tokens that can't start a type and are not whitespaces, or closing delimiters, etc)
=
<=
==
!=
>=
>
||
~
@
.
..
...
,
;
:
->
<-
=>
#
$
{
LITERAL
+
-
/
%
^
|
>>
+=
-=
*=
/=
%
^=
&=
|=
<<=
>>=
as
box
break
const
continue
crate
else
enum
false
if
in
let
loop
match
mod
move
mut
pub
ref
return
static
struct
trait
true
type
use
where
while
pub |path|
?
On Wed, Feb 1, 2017 at 5:57 PM, Vadim Petrochenkov <[email protected]
wrote:
I wish I had more constructive suggestions for the syntax.
Feel free to choose!
(Tokens that can't start a type and are not whitespaces, or closing
delimiters, etc)= <= == != >= > || ~ @ . .. ... , ; : -> <- => # $ { LITERAL + - / % ^ |
+= -= *= /= % ^= &= |= <<= >>= as box break const continue crate else
enum false if in let loop match mod move mut pub ref return static struct
trait true type use where while—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/rust-lang/rust/issues/32409#issuecomment-276810479,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAC3n_RXT3S0zZ5i_lWC7LANws1XeQN5ks5rYQ3SgaJpZM4H1fnj
.
pub in crate
, pub in some::path
, pub in super
looks neat. It reads like English, and feels consistent with equally punctuation-free pub use foo as bar
.
@pornel It looks a lot less neat in context, e.g.
pub in foo::bar::baz quux(x: i32) {
Seems pretty hard to scan quickly. Delimiters are nice.
But paths allow parens! This automatically becomes allowed:
pub in (foo::bar::baz) quux(x: i32) {
And paths are an edge case. Usual case is fine:
pub in super quux(x: i32) {
But paths allow parens! This automatically becomes allowed:
Nope! Both types and expressions have parenthetical forms, but they're not specific to paths.
@pornel I find that "usual case" also harder to scan than a delimited version. It's particularly weird because fn
can be preceded by a variety of keywords, so we are used to reading them as somewhat disconnected, e.g.
pub in super unsafe extern fn foo(x: i32) {
vs
pub(super) unsafe extern fn foo(x: i32) {
The latter feels more structured.
I like the parens because it feels like a caveat added to the pub
.
I like super unsafe
tho ;)
I'm in favour of pub (in ...)
, especially with the shorthand of skipping the in
for crate
, that feels like a not bad option. Using a sigil (@
, etc.) or a sequence of words (pub in ...
) feels harder to read and noisier than the parens option.
@solson
Anyone agree/disagree? Maybe I've missed some compelling need for pub(path), but so far I don't feel like it's a big win.
To recap a few points in the thread:
pub(self)
is a good way to add privacy to trait items.None of this precludes also having crate fn
as sugar on top.
In the interest of making progress, I'm going to propose we head to FCP, with the intent to go with pub(crate)
, pub(self)
, and pub(in path)
as the syntax, just to get the lang team on record with this. Note that this is just proposal to go into FCP; even if we do so, there will be several weeks to keep bikeshedding :-) But I think we should aim to stabilize this feature with some syntax this cycle.
If you strongly object to the proposed syntax (keeping in mind we can add addition sugar like crate fn
later), please speak up.
@rfcbot fcp merge
Team member @aturon has proposed to merge this. The next step is review by the rest of the tagged teams:
No concerns currently listed.
Once these reviewers reach consensus, this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up!
See this document for info about what commands tagged team members can give me.
Does super
need to be pub(in super)
or is it also pub(super)
?
@withoutboats also pub(super)
. Basically pub(keyword)
or pub(in path)
.
Great, I was unsure if super
is a general keyword or just a special path.
I'm in favor of stabilizing only pub(keyword)
while leaving pub(in path)
unstable & getting a sense of the impact. I'm still uncertain that pub(in path)
is a net win & I'd like to see if its still a pain point once we have the 90% case resolved.
@withoutboats
I'm still uncertain that pub(in path) is a net win & I'd like to see if its still a pain point once we have the 90% case resolved.
Can you elaborate a bit more on what negative you see from pub(in path)
? It seems to me that pub(in path)
leads to a cleaner overall mental model. That is, things are either public "to the world" or they are public "to a point in this crate (possibly the root)". If you have pub(super)
, pub(crate)
, private, and pub
all mixed in, it is a rather more chaotic picture to my mind. Or, to put another way, you have the same mental model underneath, but you lack the ability to control it uniformly.
If we were going to just go for crate
, pub
, or private, I could see that. But then (a) we lose the ability to do things like default(crate)
and (b) we fail to support pub(super)
, which is undeniably a common use cases, so to me it feels less good.
I think I must also write deeper crates than others here. I find I very frequently want to make use of nesting. Here are some examples where I would employ something other than pub(crate)
:
pub(super)
would suffice, but it's not hard for me to break up one of those modules and introduce sub-modules.@nikomatsakis
I'm not concerned about the mental model, I'm concerned about the kinds of publicity scopes we encourage users to define in their systems.
My experience has been that I have three kinds of items:
pub
)pub(crate)
)To me it seems like the question is about whether or not its feasible to implement most libraries through a pattern of recursive visibility - every module exposes a handful of types, defined in that module, to the outside world (pub(super)
), built from types defined in its submodule that only that module needs to use. Or do we need to be able to push items 2 or more levels up, which is what pub(in path)
would be for?
If people end up pushing their items up those levels using re-exports, then pub(in path)
clearly reduces the complexity of the library's API. But if we can encourage people to use this sort of recursive scoping I think their projects will be easier to onboard to.
The limit of my understanding is in the low five digits of LOC, so its plausible to me that pub(in path)
is an inevitability as a project reaches a certain size.
If people end up pushing their items up those levels using re-exports, then pub(in path) clearly reduces the complexity of the library's API. But if we can encourage people to use this sort of recursive scoping I think their projects will be easier to onboard to.
Interesting. My feeling is that pub(in path)
would indeed be rather unusual, but useful in a pinch. I am not sure that it will have a big impact on how people structure their code, though; it seems like it's already an awkward enough syntax to be somewhat discouraging.
In any case, I'm ok with keeping pub(in path)
feature-gated. My reluctance mainly stems from the fact that I dislike having "yet another" bit of obscure syntax that will linger in limbo due to the fact that, while it might be useful on occasion, it's not useful enough to have a dedicated constituency pushing for it (and yet perhaps not so unuseful as to be axed altogether).
There was one thing that @solson said that I wanted to return to (emphasis mine):
In my ideal world, pub on an item would mean "this item is exported to other crates", i.e. there is a path to the item where every module along the path is pub, and the compiler would error/warn if this was not the case....This would let a reader tell at a glance whether something is truly exported to external crates.
This last sentence sounds strikingly similar to the goals of the current system: if you see private, you know at a glance it is local. But without some lint you don't know how far something is exposed without research. I too find this suboptimal, because when I am auditing safety, I tend to want to know "how far is this exposed".
So, I guess I'm just saying that I think I would be in favor of a lint along the direction of the original priv-in-pub RFC here, that warns when an item is not "as exposed" as its publicness says it should be. I think we'd have to tinker a bit with the design: e.g., I think that if you say pub(super) struct Foo { pub f: usize }
, that doesn't merit a warning, as the field is public but it's implicitly capped by the privacy of its struct. I guess this comes back to the private-in-public discussion that (iirc) never quite resolved itself.
My reluctance mainly stems from the fact that I dislike having "yet another" bit of obscure syntax that will linger in limbo due to the fact that, while it might be useful on occasion, it's not useful enough to have a dedicated constituency pushing for it (and yet perhaps not so unuseful as to be axed altogether).
This is fair. My feeling is that the compiler authors are among the most likely to want this syntax, since the compiler is one of the largest Rust projects, and so if it has advocates they'll be well positioned.
In line with @solson's comment, I sort of wish we had a system where mod
s were public by default and so an item's visibility declaration declares it visibility without needing to know who has visibility to the module its in.
So, I guess I'm just saying that I think I would be in favor of a lint along the direction of the original priv-in-pub RFC here, that warns when an item is not "as exposed" as its publicness says it should be.
I quite like this direction, and agree with @solson as well that reading a bare pub
tends to mean "Ok, I need to trace the exporting of this item to figure out where it can be seen" today, and it'd be much nicer to read it as "exported from the crate".
One related note: pub(in path)
is less brittle than e.g. pub(super)
for internal module refactorings.
I basically agree with @nikomatsakis that in path
in a way makes the feature simpler by exposing a straightforward mental model, for which things like pub(crate)
are convenient sugar.
Path doesn't make it simpler, unless we only allow paths, but then we'd have to have longer pub(in self)
, pub(in super)
,
pub(in
… is there a path for crate? pub(in ::)?
. So path doesn't simplify it, and with or without path
version, it's better to have special cases for convenience.
Earlier I wrote:
So, I guess I'm just saying that I think I would be in favor of a lint along the direction of the original priv-in-pub RFC here, that warns when an item is not "as exposed" as its publicness says it should be.
I've been thinking about this a bit more, and I think what I would favor is probably slightly different (but closer still to the original priv-in-pub RFC). Basically a lint that checks for "consistency" in how far pub
things are exposed. I think it'd be a pretty drastic change to say that all pub
things must be exposed to the outside world -- almost all existing code would be triggering warnings -- but it'd be reasonable to say that, if they are, then all the pub
things they export as part of their interface ought to be exposed as well.
@withoutboats wrote:
If people end up pushing their items up those levels using re-exports, then pub(in path) clearly reduces the complexity of the library's API. But if we can encourage people to use this sort of recursive scoping I think their projects will be easier to onboard to.
I want to make sure I understand what you are specifically suggesting by your term "recursive scoping".
If I have a layered design, with some abstract type that has a small kernel of operations and then a family of higher level functions implemented in terms of that kernel, I was thinking I might write it like this (playpen):
#![feature(pub_restricted)]
mod a { // outermost scope for the type `T`; it should not flow elsewhere
mod b { // I have public API's that operate on T's, so re-export it
pub(super) use self::c::T;
pub(super) fn transform(mut x: T) -> T { x.incr(); x.incr(); x }
mod c { // I implement the internals of `T`
#[derive(Debug, Default)]
pub(a) struct T { repr: u32 }
impl T { pub fn incr(&mut self) { self.repr += 1; } }
}
}
fn a_foo(mut x: b::T) { x.incr(); println!("x: {:?}", b::transform(x)); }
pub fn entry() { a_foo(Default::default()); }
}
fn main() { a::entry(); }
This relies on pub(in path)
to enforce an invariant about where T
is used.
Under the pattern that you are suggesting people follow, there are two options that I see:
Make mod c
a sibling of mod b
rather than its child, like so: playpen. (This may require further revisions in the general case, depending on what private parts of b
are used by c
.)
Instead of directly re-exporting c::T
from mod b
, wrap it in a type defined within mod b
itself, like so: playpen. I think this is the closest thing to the development model that you propose. (In this strawman example it ends up requiring boilerplate wrappers, but maybe properly architected systems won't have such artificial+boring setups.)
Am I correctly understanding your suggestion? Or is there some other set up that you are envisioning using pub(super)
alone for an example like this?
One other thing I'll note: pub(in path)
may be an anti-pattern when code has reached some sort of pristine "ready for publication in an academic journal" state 😉, where each item is in a spot in the module hierarchy that has a clear relationship with its parent(s).
But when one is doing re-factorings while on that journey toward that goal state, the expressive power offered by pub(in path)
can be pretty useful for making incremental changes moving code around and gradually narrowing or expanding the scope of where the types flow to.
@pnkfelix My own pattern of behavior would have been to define T
in b
. Unless types are public (either actually public or crate
public), I try to avoid using them anywhere outside of super
, and if I need to I am suspicious of my module structure being wrong.
However I also discovered yesterday that I had not realized (or maybe remembered) that pub(restricted)
restricts re-exports; as your second comment implies, in that context, not allowing pub in path makes it difficult for module structures to exist in a temporary state of badness. This is causing me to reconsider my preference.
I think there's an impedance mismatch between what I'd like from a visibility system & what our visibility system provides. What I want the visibility statement to mean is "this is at least as visible as," but what it actually means is "this is no more visible than." There's a trade off here between making it easy on writers to guarantee their encapsulation & making it easy on readers to know if they can import a symbol.
@withoutboats wrote:
What I want the visibility statement to mean is "this is at least as visible as," but what it actually means is "this is no more visible than."
Yes, the latter is exactly the property I was trying to refer to when I wrote: "one important benefit I see of using pub(restricted)
is that someone who reads the code can quickly locally reason about how exposed the item is."
@pnkfelix But they can only locally reason about the maximum it might be exposed, which I'd contend is the uncommon use case.
@withoutboats So, just to confirm (and as a thought experiment): Is it true that under the hypothetical model that you were working under, a pub(crate) item
definition could be re-exported to the universe from any other part of the crate that could name the item? And therefore to be sure that the item is restricted to the crate, you'd have to inspect the visibility of all such re-exports (in terms of whether their location in the module hierarchy is publicly visible)?
@pnkfelix Yes, that's true. In the model I was working under a re-export "reopens" the question of visibility. I probably built this model because, in practice, pub
is really only pub(super)
most of the time because the module you're in is not pub
.
The property I think would be more valuable than the property we have would be for me to be able to look at the item that is pub(crate)
and _know_ that I can import it from anywhere in the crate. But because modules have their own visibility, today that isn't true.
And the ideal property would be to be able to look at the item and know exactly how public it is, but that is directly at odds with the kind of 'module tree sculpting' people like to do with pub use
.
I'd describe these properties in terms of ordering, '<=' (what we have), '>=' (what I would like) and '==' (what we can't definitely have).
And to expand a bit on why I don't think '<=' is as valuable as '>=' would be:
When I'm trying to write some code & there's an item I want to pull in, it is a huge distraction for me to have to trace its visibility across files to ensure that I can use it. This pulls me away from my chain of thought & disrupts my workflow. (Obviously IDEs could help here but I think that's kind of a cop out; IDEs can help with any ergonomics issue.) This is why I really think '>=' would be a great property to have.
However, I definitely see the argument that '<=' is useful also (which is why I said '==' is the ideal property). The reason I think its less valuable is that this reasoning only applies within a crate - which means anyone who has re-export access _also_ has access to the original item's definition. Validating that no re-exports have broken your encapsulation is the kind of process you do on code review, when jumping around files reading small sections of them is not a distraction but really what you're doing.
Now of course there's the concern that a collaborator on the project will re-export and break your encapsulation when you're not looking, but any such collaborator could just as easily loosen your restriction since - again - they must have commit bit to the same crate the item is defined in. I just don't know that this extra hurdle is buying very much.
That said this conversation is a little far afield from the question of pub(in path)
. I guess it matters in that with the rules we have today, I see the use of pub in path, but if we were to allow arbitrary re-export I would be less swayed.
But getting '>=' visibility would also require other changes to our visibility system which are totally unrelated to this tracking issue.
Nominating for (yet more!) lang team discussion. We should really try to stabilize this for 1.17.
@withoutboats my take on <=
vs >=
is this:
pub
should be reachable from the crate root".I am actually dubious about anybody wanting to know that something is at least that exposed. I think some users would like to know that exactly how exposed it is. In other words, if I declare something as pub(in a)
, I don't think that you want to know that at least a
can access it, but maybe the whole world can? Taken to the limit, that would imply that you are ok with your private fields (accidentally) being accessible to anyone, because you always know that at least you can access them.
@nikomatsakis I basically feel the opposite in regard to what should be enforced by the language or a lint; I'm worried we may be at an impasse there.
I agree that no one wants to know that something is "at least" that exposed, but wouldn't be satisfied knowing it is "exactly" that exposed. I think you've misunderstood me on that - what everyone wants is exact exposure, but that isn't consistent with the concept of 'facading' that people regularly do, where something is exposed from a false path but not from the real path.
What I'm saying is that given that we have to be imprecise in one direction, I think the more useful direction to be imprecise in is ">=".
When you treat it as a maximum exposure, you can deal with your safety as if it were exposed exactly to the maximum extent allowed. Assume the worst case, as it were. That's a useful way to be imprecise.
But I don't see what is useful in specifying minimum exposure. If I don't know where it ends, I have to assume it's global. You agree that no one wants to know "at least", but still think this is the useful direction?
Its important to remember that we're talking about within a crate - either way you can determine the exact visibility of an item from the source you have available to you. If you're worried about the worst case, you can check it.
As I tried to express in my previous comments, I feel that '<=' matters to the author trying to establish encapsulation, whereas '>=' matters to the user trying to establish accessibility. Inevitably, we use much more code than we write.
See this thread for some related thoughts from the most recent lang-team meeting: https://internals.rust-lang.org/t/lang-team-minutes-the-module-system-and-inverting-the-meaning-of-public/4804/
@withoutboats While mulling over your points about '<=' vs '>=', I keep coming back to a fundamental question: If the developer is trying to establish accessibility, why wouldn't they just use pub
on its own? I.e. what value does pub(restricted)
provide under '>='?
The only reason I can see to provide pub(restricted)
under a '>=' interpretation is to support things like the facade pattern, where I want to put my definition in one place, but I do not want to expose that path to the thing. so, it wouldn't become useless under such an interpretation, but it certainly becomes questionable whether to keep it as a feature, in my opinion.
Here is my attempt to summarize the situation:
pub use
, binding it to other paths; I just want to ensure this name/path is not exposed outside of a certain boundary"pub use
."Do the above bullets seem like a fair description of the situation, or am I misrepresenting a viewpoint?
Is there any chance that we could resolve this problem by actually distinguishing the above two cases (attaching visibility to name alone, versus attaching the same visibility to name and content) rather than continuing to conflate them without exception in the syntax? (I suspect not, since this sounds like it would be adding yet another feature to the visibility system, making it even harder for people to comprehend. But I thought I should at least mention this potential route.)
@pnkfelix that sounds like a good summary to me, and insightful
I had considered pub(super) priv(crate)
(meaning at least super, no more than crate), but that seems worse to me than picking one.
@rfcbot reviewed
Given that we're pretty set on not allowing pub use
to export beyond the restriction boundary, I think pub(in path)
definitely has unavoidable use cases. So I'm in favor of the entire feature, even though I hope pub(in path)
will be used with great infrequency.
:bell: This is now entering its final comment period, as per the review above. :bell:
The final comment period is now complete.
Huzzah! We have now decided to stabilize this feature. This means that we need:
I've just written up a little guide to stabilizing features that I plan to add into the rust-forge (it's not added yet, though, so use the prior link). That guide was written with this feature as an example, so it's also serves as mentoring instructions. I've thus marked this bug as E-mentor.
So... the whole discussion about inverting the module system was launched during the FCP here. Does that affect anything?
@durka We decided against the >=
semantics, I believe that all existing proposals are compatible with the current (<=
) interpretation of pub(restricted)
.
@durka The discussion started before going to FCP -- it was what held up @withoutboats from signing off. Everybody is OK moving forward now, because there's consensus that the <=
semantics is important to offer, and the current thinking around deeper changes to the module system fits in with this design.
Also it would be backwards compatible to change it to >=
in the unlikely event we changed our mind (we just have to support pub(in path)
; my objection was that with >=
pub(in path)
doesn't seem to carry all of its own weight).
I will be sending in a PR to @nikomatsakis 's guide tomorrow with some extra details with regards to docs
Anyone want to work on the docs to try to squeeze this in for 1.17?
@aturon Sure, I'd be happy to work on it. I pinged @steveklabnik on IRC with a couple of questions.
To make sure I'm covering everything, is there anything other than these items that should be updated?
Just to clarify with everyone: super
is a path, so both pub(super)
and pub(in super)
are valid?
Yes
This probably shouldn't have been closed since the documentation hasn't landed yet.
I believe this is only waiting on docs, and I believe all the docs have made it. Can this be stabilized?
This was stabilized a month ago, the only remaining bit is the Rust-by-example PR that's not merged yet.
I think it's time to close this.
Ah excellent; I was going to say, sad if it had just missed a release.
I thought it did miss the release-- it was beta nominated for a bit, but that got cancelled IIRC.
Just checked in the playground-- it stabilizes in 1.18.
Most helpful comment
I like
super unsafe
tho ;)