Rust: (Modules) Tracking issue for `crate` as a visibility modifier

Created on 6 Aug 2018  ·  91Comments  ·  Source: rust-lang/rust

This is a sub-tracking issue for the RFC "Clarify and streamline paths and visibility" (rust-lang/rfcs#2126)
dealing with the question of crate as a visibility modifier.

Unresolved questions:

  • [ ] How do we parse struct Foo(crate ::bar)?
A-visibility B-RFC-approved B-RFC-implemented B-unstable C-tracking-issue T-lang

Most helpful comment

I'm personally ok with writing pub(crate), the intent seems very explicit.
The example given by @johnthagen is really painful to see(using crate):

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,
}

crate mod color; especially seems confusing, you definitely need to put a bit of thought on what's happening there.

All 91 comments

Comments about crate as a visibility modifier:

Parsing ambiguity

Unnatural / Confusion / Not improvement

A good idea

pub(extern) instead

Bikeshed

An early preview

Dedicated thread

Personally, I am very much in favor of crate as a visibility modifier and I share @stepancheg's comment here. I think that we should encourage smaller and stricter visibilities and crate does just that.

I'm personally ok with writing pub(crate), the intent seems very explicit.
The example given by @johnthagen is really painful to see(using crate):

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,
}

crate mod color; especially seems confusing, you definitely need to put a bit of thought on what's happening there.

Some of these examples are very C-static-esque- intuitively related but really surprisingly distinct.

The example due to @johnthagen does not read poorly to me. In fact, it reads naturally and I quite like the symmetry. It is beautiful in a way.

If the readability of:

crate struct ColoredText {
    crate color: types::Color,
    crate text: &'static str,
}

becomes a problem; then an IDE/editor that understands the syntax of Rust can highlight the crate tokens in the different positions with different colors. That should clear up the difference well I think.

crate as visibility modifier is definitely weird: it uses very rust-specific keyword for a thing which is not specific to rust. Kotlin & C# use internal for this.

I'd personally would want to reuse pub for crate-visible, and go for a more screamy syntax for world visible, like pub* or pub!.

It's been brought up before, but I think the root issues I see are:

  1. crate is a noun. I think pub(crate) is long and odd looking, so I fully support replacing it with something, but it did have the public adjective associated with it, so grammatically it flowed better.
  2. crate is now used as the anchor for "this-crate" imports, which means something different than "wherever this is defined, it's also exported visibly from this crate"
// Here `crate` means the root of this crate.
use crate::menu::{Sound, Volume};

// Here, `crate` means: export crate::game::color
// The `crate` is referring to `color`, not the root.
crate mod color;

...

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

Compared to an example, using @matklad's internal from Kotlin/C#.

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

internal mod color;

...

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

I'm not saying internal is the right keyword (Rust likes it's very short abbreviations, and int is unfortunately full of C/C++/Java confusion), but I personally think that the second example is immediately more readable.

I also think the crate visibility keyword will be confusing to people coming to Rust from other languages. If even many of us involved enough in Rust to comment on these threads are jarred, I'd have to imagine it will trip up people new to Rust as well.

The slightly longer syntax of pub(crate) may not be such a big deal if weren't becoming a warning/error to have pub items that aren't reachable outside the crate. I personally wish that if I had a pub(crate) struct Foo { ... }, that the compiler could realize that all the pub fns in a impl Foo are clearly not reachable, and not bother me about it.

I find it to be just busy work currently in Rust 2015 if I ever mark a type from pub struct Foo to pub(crate) struct Foo, how the compiler then yells in all the places that some other pub fns exist using the suddenly pub(crate) type, when the problem isn't real, because the other type is also pub(crate).

I also find @matklad's idea of re-purposing pub as crate-public and using export or something for world-visible exports. But that may be too big a divergence for an edition?

Repurposing pub as crate-public and adding a new visibility for world-public was the proposal before the current version. Such a change to existing semantics was considered too drastic even for an edition, which is why pub is now keeping its current meaning.

What was less discussed and considered, I think, was repurposing pub solely via a lint. Perhaps we could switch the lint from "warn on pub that's not accessible outside the crate" to "warn on pub that is accessible outside the crate," and add a purely optional pub(extern)/export keyword. That is, don't change any semantics, just add a lint-silencing syntax.

I suspect this would be less disruptive, based on the assumption that there are fewer world-public items than crate-public items. It would also preserve the intuitive (though subtly incorrect) meaning of pub as "export from the current module" rather than confronting everyone with visibility's true behavior.

Rust likes it's very short abbreviations, and int is unfortunately full of C/C++/Java confusion

FWIW, though it only saves two characters, if we wanted to abbreviate internal, the "right" abbreviation would probably, by analogy with external, be intern. Unfortunate that that is also a noun with a commonly understood, different meaning. Oh well.

@glaebhoerl intern is a good option to consider! ❤️

The symmetry with extern is really nice, and IMO would greatly reduce the potential confusion with the noun form of intern.

It's short, (only 1 more character than crate) and doesn't clash with use crate::.

The updated example would look like:

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

intern mod color;

...

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

I've mentioned it before, but I'm not sure what the problem is with nouns used as adjectives?

To recap: there are plenty of nouns that can be used as adjectives, eg. a house cat, a computer mouse, a computer desk, etc... A google search for _english nouns used as adjectives_ seems to indicate there's nothing inherently wrong although not all nouns work as adjectives.

Let's try it:

_crate mod hello;_ A crate module named hello, feels fine.
_crate fn world() {}_ A crate function named world, feels fine.
_crate struct Foo;_ A crate struct named Foo, feels fine.
_crate enum Bar {}_ A crate enum named Bar, feels fine.
_crate trait Baz {}_ A crate trait named Baz, feels fine.

_crate use self::local::Foo;_ Ack, this one does not work, a crate use? You can read it as crate usable item named Foo. It does break the pattern.

It can also be awkward when used in front of struct members and even more in combination with crate as the root of a path.

While crate isn't perfect, I'm not convinced that 'being a noun' is a deciding factor.

The problem is that it is very uncommon. There is no programming language I know that use nouns as type modifier.

@Centril

becomes a problem; then an IDE/editor that understands the syntax of Rust can highlight the crate tokens in the different positions with different colors. That should clear up the difference well I think.

Personally, while I find features of different editors nice, I don't think we should design the language with the assumption of a sufficiently advanced editor. I felt like C# was designed this way and it was a major factor in my frustration with that language.

@epage I think crate as a visibility modifier is a good idea regardless of highlighting; I'm merely suggesting that highlighting is additional mitigation. In particular, it should be fairly trivial for any editor to highlight crate:: differently than crate field because the former is always crate + :: which is easy to check for in all cases except crate ::foo::bar (but this will be fairly rare..).

As an IDE person, I think such highlighting would add significant amount of noise for a very little amount of information, netting negative. IMO (this is highly personal, but informed by both using and implementing powerful IDEs) highlighting works best when it conveys semantic non-local information (does this usage refers to a variable that was declared with mut) and de emphasizes local "boilerplaty" aspects of code (so all keywords should be styled exactly the same).

It seems to me that dom (i.e: domestic) is a potential candidate.

It has three letters (nice for alignment, easy to remember), it's not a noun, and - other than 'Document Object Model' - I don't think it has any particular ambiguity attached to it.

pub struct MyStruct {
    dom num: i32,
    pub msg: String,
}

Does anybody have thoughts on this?

One angle on this that I've seen mentioned but couldn't find in the summary (thanks for doing that btw!) is how a shortcut fits in with the existing pub() syntax.

If pub and <something> (e.g. crate) have special meaning, it further reduces the visibility, and by extension familiarity, of pub(<something>). Whatever solution we go with, I think it should support or replace the existing machinery rather than being yet another one.

For example, if we use crate or replacement:

  • Should crate be extended to take on scoping restrictions (e.g. crate(<something>))?
  • Should we deprecate pub() so pub only has one meaning?

Considering this and my understanding of the goal (clarify public API from internal API), led me to recreate @vitiral's idea of pub(extern).

  • Fits within existing machinery
  • imo improves the existing machinery by making pub a shortcut of pub(<something>) rather than being a special case
  • If the public API is significantly smaller than the private API, then we've weighted the syntax the right way.
  • But might confuse people coming from other languages where public means it might be in your public API.

RE Impact on encapsulation

One benefit of the existing pub system is encapsulation. The easy-path is to expose API only one-level up. This makes it easier to have things public to parts of a crate but private to the whole create.

While there will still be pub(super), having a shortcut for pub(crate) will push people in the direction of using it more, encouraging people to not encapsulate their APIs.

I suspect this isn't an issue because of the culture of small crates.

But in considering this, it gives me another iteration on my above comment on pub(extern)

  • pub should be a shortcut for pub(super)
  • pub(extern) is required for your public API.

I previously brought up the concern of people transitioning from other languages. This better aligns with them.

  • More closely matches how public works in various languages
  • Some languages tend to have a distinct mechanism for public API, so this is explainable to them.

imo this is the best of all worlds. So tear it apart and help me understand why not :)

I still hate the pub(foo) syntax. Hyperbolically, it looks like it can't decide if it's a function call or a mashup of multiple keywords. We don't use let(mut) or for(in) so what's the deal with this one?

@parasyte pub<foo> for the win! After all, isn't it a _type of visibility_?

pub<crate> or pub(crate) feel better indeed.

Some thoughts from someone who changed camp:

At first I was very opposed to crate and thought "this is ruining the nice pub".

I then tried it side-by-side in some of my projects and let it sink in.

Frankly, after a few days I couldn't stand looking at pub(X) anymore, it feels clunky in comparison, and harder to read.

Initially I feared there might be (visual) ambiguity; but to me the opposite happened: If I see crate now I know it's, well, "crate stuff". Whether that is importing modules or declaring visibility. What exactly is in the overwhelming majority of cases very clear from the context (sort of like ambiguity in English).

I can see there might be still be residual "harder" (visual) ambiguity in some cases, but I wouldn't want to trade that back for what now feels like an massive quantitative readability win (as in: "source code lines that require less visual tokenization / effort vs. source code lines that became more ambiguous" ).

From that angle crate - intern (or any other asymmetry) would also feel like a step back.

Having said this, I don't know about parsing ambiguity. If I had to pick one, I'd much rather have a good story around "crate means crate stuff" than a good story around "crate ::foo::bar just works".

My two cents are that:

  • I've been using crate mod, crate struct, crate fn, ... extensively and I find it extremely useful.
  • I don't really care how it is named (crate, pub(crate), ...) as long as it isn't overly long because I use it often.

If it were up to me, I would use vis as the keyword, and the type of visibility as the modifier, e.g. vis(pub), vis(crate), etc. because this makes more sense to me.

Given that we are already stuck with pub as a "visibility specifier", I actually like crate. The pub(crate) reads for me as public to this module, private to the crate - I find using both public and private at the same time here weird.

Introducing new keywords and contextual keywords, etc. is in my opinion not worth it. Teaching that there are two visibility modifies pub and crate, and that one means public, and the other means private to the crate, kind of makes sense to me, but maybe I just got used to crate already during the last two weeks.

To those that suggest an already used keyword (i.e: crate) conflates meanings, I'd argue that context is more important than the word itself. The brain parses everything with context (how the compiler parses this is a different matter): this explains why we don't conflate the semantic meaning of for in for x in y and impl X for Y.

Equally, introducing crate as a visibility qualifier would likely not create confusion because its meaning, in the context of a member or function qualifier, is obvious when provided with that additional context. For example, crate fn my_func(); doesn't read as "this is a crate", it reads as "this is a crate-visible function".

That said, the fact that it is a noun is inconsistent. I'd be for creating a new visibility qualifier keyword to address this issue.

In fact, if there's anything that definitely will confuse users, it's the pub(crate) syntax which intuitively looks like a function call and has no other syntactic equivalent elsewhere in the language. To me, it feels like an ugly hack.

As someone who's brought up concerns about using crate as the replacement for pub(crate) and after reading @aturon 's latest post:

Supporting crate as a visibility modifier (tracked here 138. Given feedback so far, this feature is unlikely to be stabilized for Rust 2018.

I just want to make sure I'm clear that I am personally fully in favor of replacing pub(crate) with something (as I think the majority is).

In order of preference, with what I feel would be most easy to grasp, especially for those new or unfamiliar with Rust:

  1. intern (or some other similar new keyword)
  2. crate
  3. pub(crate)

If the core team feels like intern or something similar would ultimately never be accepted, then I'd get behind crate as I still think it's a big improvement over pub(crate), for the reasons @Centril and others have articulated.

So I wouldn't stand in the way of this if the, much more experienced, core team feels this is the best path forward. It's neat to just be able to provide feedback express ideas for consideration. 👍 Rust!

@ralfbiedert

Initially I feared there might be (visual) ambiguity; but to me the opposite happened: If I see crate now I know it's, well, "crate stuff". Whether that is importing modules or declaring visibility. What exactly is in the overwhelming majority of cases very clear from the context (sort of like ambiguity in English).

@zesterer

To those that suggest an already used keyword (i.e: crate) conflates meanings, I'd argue that context is more important than the word itself. The brain parses everything with context (how the compiler parses this is a different matter): this explains why we don't conflate the semantic meaning of for in for x in y and impl X for Y.

I'm not meaning to call you two out personally but serve as examples of the people in favor of the change after using it.

While I find it weird looking, my biggest concern is with non-rustaceans.

  • How might this impact someone's opinion in evaluating rust? One thing syntax quirk shouldn't be enough to dissuade them but if you pile it on with lifetimes and any other "odd" thing, it might discourage people
  • How does this impact people learning Rust?
  • How does this impact people who don't know Rust but need to modify it?

I'd love it if we could do usability studies on this to know better how much impact these concerns have.

@epage, very much agree, the user-facing parts of Rust should be UX tested. However, I think this is what is happening right now, and we are in the middle of discussing the results.

To add to that, some anecdotal observations from our company:

I am the "Rust advocate" in our department, working with 3 others. All have a solid background in C#, but are relatively new to Rust. The other day I migrated our research project to Rust 2018, along with the "crate-stuff".

When we went through the code, the conversations went approximately like this:

"So here are some other changes I made; new import system; modifiers."

"What does that do?" (pointing at use crate::object and crate x: object)

"Import from this crate." and "Visibility modifier."

"Ah, ok. Anything else that changed?"

End of discussion.

Surely, we chatted a bit more about new features, and whether we should adopt certain patterns; but the "teaching aspect" of these two items boiled down to a few words and has not been mentioned since (to my knowledge).

If I hear anything else the next time we talk I will update this comment.

@ralfbiedert Thanks for sharing that!

@epage, very much agree, the user-facing parts of Rust should be UX tested. However, I think this is what is happening right now, and we are in the middle of discussing the results.

While I value these UX anecdotes, especially for the learnability side, I don't think I'd qualify this issue's process as UX testing. My comment is referring to a more formal process that can help gain deeper understanding, weed out biases, etc.

Here are a few thoughts coming from a Rust somewhat-newbie. First off, I want to say that something which really feels sane and nice with this language is the "immutable by default, private by default" approach.

Now, pub is nice because it's simple and expected in modern languages. I feel that having to un-learn this in the context of Rust and sprinkle another keyword everywhere instead is a bit clumsy. Semantically, it just feels right for it to mean "this is a button that appears on the box", the box being the module: visibility "from one level up".

So, to me, using crate or another keyword of that nature instead of pub just feels itchy: if being public is the non-default, being exported out of the crate should be even more special. That is, the crate's API should be denoted in a special way.

So, I wholeheartedly agree with @epage, pub should stay the same and a pub(extern) of some kind be introduced. Parenthesized keywords really feel hairy though, so maybe it'd deserve a dedicated keyword. crate keyword would work in that sense, I can see it meaning "this an exported crate member". Or export actually, I don't know. Maybe all of my point is bikeshed though, and this all amounts to "the keywords aren't right". But pub is so common that it doesn't feel special, so it shouldn't represent something really special (the crate-exported API).

I watched a talk at RustConf this weekend that used a lot of pub(crate) in its code samples, and it really really made me wish for plain old crate. I am still very pro the original plan.

@steveklabnik

I watched a talk at RustConf this weekend that used a lot of pub(crate) in its code samples, and it really really made me wish for plain old crate. I am still very pro the original plan.

Is the context of this comment mostly from RustConf or does it does it take into account this thread and presuppose a disagreement with it? Earlier, I provided an alternative solution, not to pub(crate) but to the requirements driving any of the pub changes and I am hopeful it would satisfy people's needs.

See

@superseed

So, I wholeheartedly agree with @epage, pub should stay the same and a pub(extern) of some kind be introduced. Parenthesized keywords really feel hairy though, so maybe it'd deserve a dedicated keyword. crate keyword would work in that sense, I can see it meaning "this an exported crate member". Or export actually, I don't know. Maybe all of my point is bikeshed though, and this all amounts to "the keywords aren't right". But pub is so common that it doesn't feel special, so it shouldn't represent something really special (the crate-exported API).

RE "Parenthesized keywords really feel hairy though"

While personally I thought they were neat when I found out about them (much better than all-or-nothing friend), my bigger concern is we don't create a parallel syntax but either embrace what we have or find an alternative solution.

On the other hand...

RE Or export actually,

I don't think adding an export contradicts my earlier comment in contrast to crate. In this context, export is could be treated as different than visibility. export would imply pub(crate). I suspect this won't have much issue in teaching.

I can go either way on this extension of my original idea.

@superseed

pub […] visibility "from one level up".
crate keyword would work in that sense, I can see it meaning "this an exported crate member".

I think that your understanding of the meaning of these two keywords may be the exact opposite of what is proposed here, which is that pub means public to everyone while crate means accessible from the same crate.

@epage

In the case of pub(crate), it does offer a really neat feature, and actually reads well, but looks too much like a function call in my eye. i.e. without syntactic highlighting I'd probably be really confused, and highlighting shouldn't be necessary to understand the semantics of a language.

@SimonSapin

Indeed, and I realize that's the way it's supposed to be understood, but crate -- being a noun -- feels like it is declaring either a crate (?) or a property of the crate. Emphasis declaring and not qualifying.

And public/pub is such an ubiquitous qualifier, it doesn't feel (to me!) like it should mean "this is exported out of the crate". It has this meaning of "this is visible from just outside what context I'm in", as is the case with its use in qualifying struct member visibility (and correct me if I'm wrong but I don't think the semantics are changing in that case).

And public/pub is such an ubiquitous qualifier, it doesn't feel (to me!) like it should mean "this is exported out of the crate". It has this meaning of "this is visible from just outside what context I'm in", as is the case with its use in qualifying struct member visibility (and correct me if I'm wrong but I don't think the semantics are changing in that case).

pub has always meant "this is exported out of the crate"- this isn't a change, it's how it already is. The fact that so many assume otherwise is why the pub(crate) visibility level is being pushed at all.

pub has always meant "this is exported out of the crate"

I think this understanding may also cause some confusion, because it's not the full picture. pub truly means "I don't care who outside of this module accesses this item/field/method". To be exported from the crate, it still requires that a path to the item also has the same pub modifiers on them.

This detail is pretty usual in many languages. It's also why I'm not keen on the unreachable_pub lint, since that is part of what is pushing this issue so much. If I have a type that is never exported at the top level, then hassling me that I marked it's methods pub just feels like noise.

@rpjohnst Is it really what it always meant? Wasn't it the chain of "visible from super" from the top of the crate which made an element exported, and not qualifying the leaf element itself as pub?

No, that's not the whole story, and @seanmonstar's clarification hints at the rest. The biggest exception is reexports- you can pub use something whose parent modules are private. A weirder example is something like this, where you're allowed to use a pub item in a public interface even if its parent modules are private.

And going in the other direction, pub(crate) and lesser visibilities can't be bypassed in the same way- you can't pub use something that's not already pub, even if the pub use is not itself visible from outside the crate.

So an item's own visibility is not directly about its visibility to super, but its "upper limit" of visibility anywhere.

Oooh okay, thanks for the clarification! I had a much more naive model in mind. It makes more sense regarding previous comments about "the current meaning of pub".

We discussed briefly today in @rust-lang/lang meeting:

  • Many of us feel positive about this but there are lingering doubts about the choice of keyword, and whether it can create confusion when combined with crate::foo::bar paths
  • However, it's worth pointing out that we do have to decide what sort of way to resolve struct Foo ( crate :: foo :: Bar ) -- is this a private field of type (crate::foo::Bar) or is it a crate field of type ::foo::Bar?

    • I'm honestly not sure what we parse it is as today

Answer: we parse it as a path (playground).

This seems.. probably ok to me, because I think that ::foo::bar paths will become increasingly rare.

Many of us feel positive about this but there are lingering doubts about the choice of keyword, and whether it can create confusion when combined with crate::foo::bar paths

@nikomatsakis are there meeting notes or a summary for us to catch up on? Within this thread, I've not seen discussed at least one of my concerns[0] nor much discussion on the counter proposals. Maybe [0] and some of the others were discussed in the various internals threads but that is a lot to dig through.

[0] creating a parallel visibility syntax pushing pub(...) into obscurity with a feeling we should either remove or embrace pub(...)

@epage

@nikomatsakis are there meeting notes or a summary for us to catch up on?

No sorry; We did not discuss it for very long (a few minutes at most) and did not write down any meeting notes about it.

@eddyb briefly alluded to my as a shorter and more ergonomic visibility modifier.

I think I said mine but my is even shorter, lovely!
(For the record, I was half-joking in the meeting)

EDIT: if my is crate-local, can we replace pub with our? e.g.:

our struct Foo(my FooImpl);

(For the record, I was half-joking in the meeting)
if my is crate-local, can we replace pub with our?

Perl: making jokes a reality.
https://perldoc.perl.org/functions/my.html
https://perldoc.perl.org/functions/our.html

my is nice (and has come up before), the thing is that it's not any clearer than local, internal, or whatever, with respect to to what (or in its case, whose), which is the whole problem.

The awkward situation we're in is that we want to have three privacy levels -- "completely public", "completely private", and "somewhere in between" (i.e., crate-level) -- and due to backwards compatibility constraints we're stuck with the first of these necessarily being pub, the second being the implicit default, and having to come up with something new for the third. And English does not have many words which denote "neither completely global, nor completely local, but somewhere in between" with precision.

And the crate keyword is the one which does satisfy this, because it says right in the name what the actual scope is -- it's the crate. But (before you break out the "I knew it!"s), the price of this is that it's no longer evident that it's a visibility modifier. "Pub" is short for "public", that, one can intuit. But no other language has the concept of a "crate" (with that name); to have any hope of making sense of crate struct, one has to first learn about this.1 It requires us to make a further withdrawal from the "language strangeness budget", and opinions may differ about whether the balance is still positive.

Meanwhile pub(crate) solves both problems -- it tells you that it's a visibility modifier, and it tells you what the scope is -- but in exchange it's long and awkward.

So that's basically the shape of the predicament.

1 (Someone above described an interaction which went, "they asked what crate meant, I told them it's a visibility modifier, and that was the end of it". The problematic, and possibly more common, situation is when you don't happen to have a Rustacean sitting next to you.)

FWIW, I'd be totally okay with our or my for crate-local items.
our is even a three-letter keyword and aligns nicely with pub ~and with Perl~.
Is the problem that they sound too informal (for native English speakers?)?

How do people feel about intern? It's one character longer than crate, but other than that I think it will be more intuitive than crate to people new to Rust and has some nice symmetry to theextern keyword.

IMO our and my have the same weakness as local and internal: they're not very clear about their scope. local in particular is quite confusing, since the scope would be radically different from local variables where local means private to the enclosing scope, which for an item would be the module, not the crate. I find internal a bit unspecific. Internal to what? The module? The type? The crate? It may be obvious for those who come from languages where it is used but it won't necessarily be for others. our and my are even more vague. In contrast crate is very clear about the scope.

Regarding pub(extern), I actually have a question. Does it make any sense to ever have extern "C" fn foo() {}, without the pub in front? Because if not, we could reuse extern fn foo() {} for our regular, non "C" abi Rust functions as well. I was thinking that we could unify that and not keep the extern syntax special for FFIs. That would mean extern now desugars to pub(extern), pub stays the same as today but accepts an optional ABI string when the item supports it, and a lint for pub items being exported without pub(extern) or extern.

extern fn foo() {
    println!("Just called a Rust function from Rust!");
}

#[no_mangle]
extern "C" fn foo_from_c() {
    println!("Just called a Rust function from C!");
}

I haven't seen that mentioned in the threads I've read so apologies if this has been discussed before!

Does it make any sense to ever have extern "C" fn foo() {}, without the pub in front?

Yes- sometimes you only want to use foo as a function pointer, for example. And in fact the syntax extern fn foo() {} can't be reused for this anyway because extern without "C" defaults to the C ABI, and this is considered idiomatic at least by some.

Here's a suggestion that came up long time ago - maybe we can give it another chance now that some aspects of the new module system have been crystallized?

// Public to the world.
pub struct Foo;

// Private to the crate.
priv struct Foo;

// Basically not visible at all (only inside the module).
struct Foo;

I believe this makes sense if one thinks of:

  • "public" as "public to the world"
  • "private" as "private to the crate"
  • "no visibility modifier" as "basically not visible at all"

Some people had a knee-jerk reaction to priv not being the most restrictive one, but I want to make two points about that:

  1. In Rust, modules are used to organize things under namespaces, while crates are used to define interfaces. So if a crate is a "unit of API", then it makes sense for visibility modifiers to talk about crates primarily.

  2. I think Java made a mistake of private meaning "private to the class" and no visibility modifier meaning "visible inside the package". To me, it makes more sense for no modifier (i.e. the default) to be the most restrictive.

@stjepang I would argue that in most other circumstances "private" is going to have a more restrictive connotation than an unmodified state. The private-default-public spectrum in a general sense is analogous to protected-available-advertised.

A private club is more exclusive than a club.
A private act implies that some effort was taken to hide from general view.
A private thought should be a redundant concept given the failure of evolution to endow us with telepathy, but most people recognize it to mean a thought intended not to be shared.

As an inexperienced language learner, I would also suggest that an additional keyword is less of a cognitive burden than a single keyword with multiple context-dependent meanings. See Costello, L and Abbot, B "Who's on first", 1938.

I feel that my and our would have connotations we don't want, leaving aside the difficulty of reserving these as keywords. They make for an amusing joke, but I don't think we should go that route.

I honestly feel like people would learn what crate visibility means. I think the bigger problem comes from code that becomes difficult to read or ambiguous to parse:

crate struct S(crate crate::Foo);

crate struct S(crate ::Foo);

I personally don't see those as showstoppers, but they're definitely legitimate concerns here.

There's a parallel of pub(path) in the Scala language, which is private[path], which acts pretty much the same. It reads slightly differently, saying "this item is private, only allowing people within $path to view it". But making something private in Scala requires an annotation, as the default is public, which isn't the case in Rust.

It occurs to me that the C++ concept of friends is also similar to pub(path), as another point of precedent.

Ultimately, the issues are:

  1. the crate keyword is used for both relative paths imports and as a visibility modifier,
  2. the crate keyword prevents a unified visibility modifier syntax like pub(...),
  3. some dislike the pub(crate) syntax (too long, looks like a function call).

After 5 minutes of really deep thinking, I came up with the following... :P

Special Syntax for the Visibility Modifiers

_(using @ as an example)_

@pub use crate::Foo;

@crate struct Bar;

I personally find that quite ugly and I would not want to type either @crate or pub(crate).

Distinct Visibility Modifiers Keywords

Since there's already the bare pub and extern, I think the crate keyword fits in quite well (unsurprising when coming from languages with the usual public, protected, private keywords).

crate struct Foo;

crate fn path() -> PathBuf { ... }

But as I said, that doesn't work well with the crate import prefix and I'm starting to feel like we got this backwards. I feel the real issue lies with the path clarity changes, for instance, with no syntax highlighting:

use crate::utils;

looks like crate hasn't any special meaning.

With great inspiration from the declarative macros syntax, instead of finding some kind of unified syntax for visibility modifiers, I would rather have the following:

use std::io;
use std::path::Path;

use log::info;

use $crate::utils;

crate fn hello() -> io::Result<()> {
    utils::rm_rf(Path::new("/"))?;
    info!("Goodbye, World!");
}

(self, super and crate special anchors for paths imports would need a prefix, for instance, $crate, which makes it quite clear they are special and look like variables)

Convoluted example:

crate struct Foo(crate crate::Bar);

becomes:

crate struct Foo(crate $crate::Bar);

It appears there's a duplicate tracking issue: https://github.com/rust-lang/rust/issues/45388.
Closing that one in favor of this one.

Is there a reason the rustc implementation is dogfooding this feature? Every instance of crate fn could be changed to pub(crate) fn and stop relying on an unstable feature AFAICT.

I don't see a good reason why we'd do that. Both Clippy and rustc use unstable features all the time and they should because it allows us to test them more extensively both in terms of catching bugs in the implementation and because we can get a feeling for how it is to use them.

In this case, usage of crate as a visibility modifier in Clippy and in rustc is in my view showing that it reads better than pub(crate) and that most of the issues referenced in this tracking issue are non-issues in practice. I think https://github.com/rust-lang/rust/issues/53120#issuecomment-413466129, https://github.com/rust-lang/rust/issues/53120#issuecomment-414392549, and https://github.com/rust-lang/rust/issues/53120#issuecomment-413498376 also suggest that crate as a visibility modifier works well outside as well.

As it works well in practice, since many of us in the language team feel positive about it, and because it was RFC accepted, I think we should consider stabilizing crate as a visibility modifier after considering how we should parse struct Foo ( crate :: foo :: Bar ) (currently as a path, and probably that's right).

FWIW, I use this feature in cargo-n64 and it has been very nice!

I use crate in rustc all the time because it's short and doesn't have parentheses like pub(crate).
I still don't like how it reads though.
It also doesn't have 3 letters, so formatting changes together with pub <-> crate changes.
I still like our unironically.

@petrochenkov My understanding of what you wrote is that you prefer crate over pub(crate) but would like something better than crate as well. Is that an accurate assessment?

FWIW, I still think intern is a good option.

  • No parentheses
  • Shorter than pub(crate) (though one character longer than crate)
  • Reads better (IMO) than crate (which is used for other things like paths, e.g. crate::foo::bar)
  • Has nice symmetry with existing extern
  • Familiar, as other languages use something similar (internal). Kotlin, C#, etc.

That being said, I also agree that pub(crate) should be replaced with something.

well it's settled then-- three letters are nice, and "intern" is nice, so... int. ;)

Could we please have a lint for the crate ::T (with a space) case?

@Centril A few of us were just discussing the way forward on #rocket. Based on your recent-ish comment, is there a possibility of a proposed FCP in the near future?

@jhpratt I've been meaning to do a write-up and eventually propose it but I've been busy with other things. I'll try to find some time in the near future.

So, I was just browsing issues, looking for something about macros, and found this one.

Now that we have pub(crate) in stable for a long time already, I wonder, shouldn't this issue be closed?

Interestingly, this just came up at a recent lang-team meeting, where as discussed this as a possible issue that we'd like to revive.

Speaking personally, I definitely miss being able to write crate fn and crate foo: T on fields and that sort of thing. It's not a big syntactic diff from pub(crate) but I find it makes the code much more readable, particularly in structs that have a lot of public fields. I also find it contributes to a reasonable helpful, simplified "privacy model" --

  • structs, fields are either local to a module (very narrow reasoning)
  • or they are used somewhere within the current crate (crate, have to ripgrep around)
  • or they are public to the world (pub)

In my view, there is some intersection then between this keyword and the changes envisioned in https://github.com/rust-lang/rust/issues/48054, and I would prefer to ensure that we adopt them together. I forget the details but I remember that there were errors that one would get when trying to put the above model into practice.

I think the first step towards this would be for someone to try and do a write-up documenting the history and making sure we've highlighted all the concerns that were raised.

One specific concern that I do remember is the syntactic ambiguity of

struct Foo(crate ::x)

Today, this is accepted and crate ::x parses as the path crate::x, but plausibly the user meant crate to serve as a visibility modifier with ::x as a path.

I would be inclined to re-introduce the crate modifier and to keep the parsing of the above case just as it is today. Since Rust 2018, ::foo paths have been largely deprecated -- they still exist and can be useful in certain specific contexts, like macros, but most of the term we now encourage absolute paths that look like crate_name::b, where crate_name might be either the keyword crate or the name of some other crate. So it's pretty likely that crate::x (ignoring whitespace) actually was meant as a path and hence the current parsing is correct.

If we want to address the potential user-confusion, then I think a whitespace-sensitive lint is a reasonably good idea. In other words, struct Foo(crate ::x) would warn, but struct Foo(crate::x) would not, although both of them are accepted and equivalent.

Personally, I prefer the unified syntax and simple lookahead(1) parser more; Also, a crate is a crate, and there's crates.io out there, and all that stuff has nothing to do with visibility _directly_ — only in context of pub(_).

But well, what do I know? It's just yet another point of view. You guys are undoubtedly have more experience and more valuable opinion.

@nikomatsakis Curious if you had any thoughts on intern for this use case (or is reserving a new keyword out of scope at this point?).

After a couple years, my feeling is still the same: optimize for readability. I find that I read code much more than I write it. And so, my opinion is that it's clearer to me to read pub(scope), which can be pub, pub(crate), pub(super), pub(in proto::h1).


However, I don't think we're really adding anything new to conversation with these opinions, I'm sure we've said it all in earlier comments. Should the decision be based on something new? Or said another way, how do we decide that now the decision to adopt this syntax should be yes when it was no or postpone a couple years ago?

I also find it contributes to a reasonable helpful, simplified "privacy model" --

  • structs, fields are either local to a module (very narrow reasoning)
  • or they are used somewhere within the current crate (crate, have to ripgrep around)
  • or they are public to the world (pub)

I fail to understand the understand the benefit of this simplification and how it is worth the loss in expressive power. For what it’s worth I often make items public to their parent modules (so that sibling modules can use them) but not the entire crate. Public to the grand-parent module less so, but still occasionally.

I feel that deprecating pub(super) would be a significant loss.


Separately, I’m not thrilled about using the noun crate as a qualifier that modifies an item that can exist independently. For comparison: pub(lic), unsafe, and const(ant) are adjectives. They nouns already used as keywords in item definitions are not qualifiers but indicate the nature of that item: function, trait, module, …

Crates are already a concept we deal with, but in this proposal an item definition that starts with crate does not define a new crate.

A tiny new bit of info:

rust-analyzer provides completions and assists for adding pub(crate), which (subjectively) make it much less annoying to type, at three keystrokes.

I agree that little new information is being added here. I think a good next step, if we want to champion the proposal, would be to go back, summarize the outstanding concerns, and bring it before the lang team to try and reach a final decision. I would certainly prefer to either accept or reject this proposal, I am tired of having it hang in limbo.

From quickly looking over the entirety of this issue, I think @nikomatsakis's recent comment sums things up pretty well. Aside from a select few people still supporting things like intern, crate seems to be the most logical (and was accepted in RFC 2126). The only truly outstanding issue is how to parse fn foo(crate ::bar).

A warn-by-default whitespace-sensitive lint seems the most logical place to start. crate ::bar is currently parsed as a path, and would have to stay so in both Rust 2015 and 2018. I'd imaging it would be a candidate for a breaking change in an edition, however.

The crate ::bar case is entirely unimportant in practice and already behaves consistently with the rest of the language. It has consistently been the red herring of the discussion, please don't focus attention on it.

I would certainly prefer to either accept or reject this proposal, I am tired of having it hang in limbo.

I think I'd vote for the rejection.
I've been consistently using crate instead of pub(crate) in rustc and despite being shorter it still looks out of place most of the time, especially on fields or imports, and choosing between crate and pub(crate) feels like choosing between two evils while not being sure which is the lesser one.

Yeah. What's more important, is what @SimonSapin mentioned that crate bar indeed does not define a new crate, despite it reads like if it does.

@petrochenkov You don't think a lint makes sense? Personally, if I saw crate ::bar without seeing this discussion, I'd expect it to behave like pub(crate) ::bar. I think allowing whitespace in paths _at all_ is confusing.

@jhpratt It would be hard to deny whitespace between path segments, due to the nature of how parsers work with tokens. Also it would break insane amount of tools like quote!, syn and many others.

I think I would support closing this as well. I've seen enough confusion expressed about the meaning of this construct that I don't think the brevity gain is worth the potential loss to readability.

This may fit better in the "larger" (but closed) tracking issue #44660, but it's also directly related to visibility modifiers and doesn't fit the other sub-issues of #44660:

At some point I remember someone suggesting that pub(path) could now be legal, replacing the pub(in path) form and subsuming pub(crate) and pub(super). That seems to be a nice simplification of pub(...), though in a different direction than crate, that we might consider as well.

Edit: Actually not sure that this works... struct S(pub(path) Type) is ambiguous (to LL-esque parsing) with things like struct S(pub (Type,)).

So I've been giving this thought over the last few months. I think I've come around to the "close" position. I think the summary of my position is as follows:

  • The idea of "three levels of visibility" (module, crate, world) is relatively simple, which is appealing, but it does miss out on common real-world cases. pub(super), in particular, I find to be really quite common in practice, though it's rare to require more than one level (i.e., pub(crate::foo)). For example, I often want to have modules that have sub-modules, and using crate visibility for those details fails to communicate the intended level of privacy.
  • The model of "local to some part of the crate, or public to the world" is also conceptually simple and elegant, and it covers all those use cases.
  • pub(crate) fn, while significantly less concise than just crate fn, is not so bad. It's cute that pub(crate) extends to pub(crate::foo), which covers the other use case I sometimes get (i.e., "big" modules within a crate), but that's so verbose I suspect it will rarely if ever be used, and quite likely those big modules would be better off factored into subcrates anyway...
  • I think the biggest ergonomic hurdles around "mixed levels of privacy" came from lints and errors related to us trying to ensure that (e.g.) all types that appeared in a pub(x) fn were of suitable privacy. We've tweaked those rules and I just feel like I haven't been hitting those annoyances anymore -- and if more such annoyances come up, I think we can address them too.

    • For example, it's possible to write pub on fields to mean "as public as the struct itself", and I think that works out just fine.

And, of course, the fact that not adding crate fn now doesn't mean we can't add it later. In particular, I think that the "three levels of privacy" model would work better if we had some way to do "lightweight, inline crates" within another crate. i.e., if instead of having a submodule within the crate, I could declare a private crate with all that implies (most notably, probably, a DAG-like relationship to other things). I'm not sure if lightweight, inline crates would actually work, but they might cover and replace a lot of the pub(super)-like use-cases. If ever were to explore that, then I'd consider re-opening the discussion around crate fn, as it might become significantly more useful/common.

In short, I would be in favor of removing the crate visibility level from the compiler and removing the feature gate.

I just wanted to check on when this feature might be stabilized and am surprised and disappointed that there are plans to remove it. I've been using it on a day-to-day basis since not long after it was added and I'm hardly bothered by any of the issues discussed above. I still haven't written anything resembling crate ::path, for example, and probably never will since I never touch the ::path syntax.

There's room for improvement, sure. The keyword could be better chosen, pub(super) is still inconvenient, and it's a nuisance getting dead code warnings all over the place when I use crate-level visibility for helper methods. As a user, though, I would prefer for this feature to be left around (feature-gated) until a better solution is found. The pub(crate) syntax is something of an eyesore and slightly discourages breaking up big modules into smaller, tighter ones.

think the biggest ergonomic hurdles around "mixed levels of privacy" came from lints and errors related to us trying to ensure that (e.g.) all types that appeared in a pub(x) fn were of suitable privacy.

Does this mean that we also don't enable "unreachable pub" lint (mod private { pub fn f() {} })?

Heh, I've been having second thoughts as I read through various of rustc code and see crate in use, and I imagine that those cases were all pub(crate)... would probably be worth doing the transition experimentally just to see how the diff makes me feel. I remember feeling sad about it for chalk when we moved it from nightly to stable, though I think now that I'm used to it it hasn't bothered me so much.

@matklad I hope not, I think that lint is quite important too. To be honest, I'm not entirely sure why I haven't encountered any kinds of errors and annoyances of the kind I used to get. Maybe I've just not been writing enough Rust code lately! I definitely remember that I used to have these annoying cycles where it seemed like I couldn't satisfy the compiler except by making way more things pub than I wanted to.

What about keeping pub(crate) and also adding an alias pubc? This reads quite similar, does not break any current code, and removes the necessity of typing parentheses (what makes it a bit faster).
This would also allow pubs for visibility in the parent.

In short, I would be in favor of removing the crate visibility level from the compiler and removing the feature gate.

I find pub(crate) to be kind of an eyesore and noisy when I'm reading code. It would be really nice to have the crate visibility modifier instead.

And, of course, the fact that not adding crate fn now doesn't mean we can't add it later. In particular, I think that the "three levels of privacy" model would work better if we had some way to do "lightweight, inline crates" within another crate. i.e., if instead of having a submodule within the crate, I could declare a private _crate_ with all that implies (most notably, probably, a DAG-like relationship to other things). I'm not sure if lightweight, inline crates would actually work, but they might cover and replace a lot of the pub(super)-like use-cases. If ever were to explore that, then I'd consider re-opening the discussion around crate fn, as it might become significantly more useful/common.

I would definitely like these "lightweight" crates that you mention! It would be a lot nicer than immediately going to workspaces, which are kind of heavy.

Was this page helpful?
0 / 5 - 0 ratings