Pub(crate) keyword suggestion

This is mostly a low-effort keyword name suggestion, but, as I didn't think of/notice it for the some amount of previous years, I decided to document it for posterity. Definitely not a "we should" suggestion, just "in theory we could". To cut to the chase:

api to me sounds like a nice keyword to capture "visible outised of the current crate" semantics:

/// Public API of the crate, guarded by semver, guarded by "unreachable_api"
api fn foo() { }

/// Not a part of the crate's API, but otherwise accessible throughout the crate
pub fn bar() { }

/// Private, visible only in the current module
fn baz() {}

// part of semver
api struct Widget {
  api name: String, // also part of semver
      id: u32,      // clearly not semver
}

That is, in edition X we can introduce api keyword, which is synonym with today's pub, except that it also has unreachable_api as deny-by-default. In edition X+1, we re-purpose pub to mean pub(crate).

10 Likes

There used to be the crate keyword functioning the same as pub(crate), but it was removed in Remove feature: `crate` visibility modifier by jhpratt · Pull Request #97254 · rust-lang/rust · GitHub The following reason was given:

Given the ambiguity observed with struct X(crate ::ident), we don't think crate is going to happen. If someone wants to champion a new keyword or a new approach, they should start an MCP for that.

After thinking about it, I really like this keyword. It's logical even if a bit unusual. It's 3 letters just like pub. I think pub(crate) is annoying to write and causes a lot of visual noise.

As a side note, if this was implemented, public dependencies should also be called "api dependencies" instead.

2 Likes

Rather than make api a new keyword, why not just require pub(api) instead?

1 Like

api is a very common identifier. Reserving it as a non-contextual keyword would be highly disruptive to the ecosystem. Having it be contextual and only breaking struct X(api ::ident); still seems likely to be an issue, in crates that have a top-level mod api; (I'm not sure if this is the only situation that would be impacted by a contextual visibility keyword, just that it is one that would be).

5 Likes

pub(whatever) is still more cumbersome to type and visually noisier than a simple standalone keyword.

2 Likes

But api can still be a context keyword if it's followed by an identifier or other keyword, like api fn versus api.x += 1;. api fn isn't a valid expression.

My language and TypeScript do this very commonly. For example, there type is a context keyword.

Personally, I consider adding resistance to making something api-public a good thing. For libraries, making something api-public should be a deliberate decision. And this doesn't change anything for binaries, which can still just use pub for everything.

Also, api just doesn't stand out to me as a visibility modifier.

1 Like

I agree lexical api doesn't fit at all, but to me this idea reminds me of ActionScript 3 annotatable definitions (where an arbitrary identifier can be used as access modifier).

As my comment went on to say that still causes ambiguity in (at least) the same position as having crate be a keyword with two different contextual meanings does.

I don't think it needs to be burdensome to force people to think about their public API. In the Rust ecosystem it's already well-known what kind of committent this is.

OTOH api being locally explicit makes it clearer what actually is part of the public API, as opposed to pub that may or may not be the public API, depending on on accessibility of parent modules and re-exports.

So I think api helps people think "do I really want this to be in the crates API?" as opposed to mistakes like "I'll write pub, because pub(crate) two shift presses too many, and hope this type isn't exported".

This is because the keyword is without a precedent in Rust or other languages, but inherently it's not any different from other 3-letter acronyms, and I think it will stand out enough once people get used to it, and editors highlight it as a keyword.

And it doesn't need to be a strictly reserved word. For example, will it break compatibility if Rust introduces api as a context keyword followed by an identifier or another keyword? I guess not, because to start with, api blahblah isn't valid anywhere in Rust. In F# for example api blahblah is something, so F# can't do this (it's a call expression).

1 Like

but api ::blahblah is -- it's the same problem as crate.

2 Likes

I said identifier "lexically", not a identifier expression (including punctuator tokens).

api ::blahblah consists of 3 tokens:

  • api
  • :: (punctuator)
  • blahblah

api blahblah consists of 2 tokens:

  • api
  • blahblah

And like I said, TypeScript does the same for the type context keyword. My language too, including for enum.

So no need for a new "Rust edition" for something that can be a context keyword.

I don't see how that's supposed to prevent the issue of struct X(api ::ident). This is a context where a visibility is expected, so api being a context keyword doesn't prevent the ambiguity. Also note that both api::ident and ::ident are valid paths, so that can't be used to disambiguate.

I think you don't understand how parsing works. It's a bit tricky to explain, and you seem to think that :: is part of the identifier. Anyway, context keywords just work in this case.

Here's my parser: violetc/Parser.cs at master · violetscript/violetc · GitHub

Somewhere you'll see that I've a method to determine if a statement or directive starts with a set of strict reserved words or context keywords and more and more. It's a bit tricky though.

It doesn't matter whether :: is part of the identifier or not, today you can write struct X(pub ::foo::Bar);. If api is to replace pub then it must be possible to change this to struct X(api ::foo::Bar);. Which means that if api is a contextual keyword it must be a keyword in more situations than just "api" IDENT (some of which other situations are ambiguous).

Are you saying pub is usable out of just, for example, "pub" fn IDENT? Like pub::x is a valid expression? I didn't know. So you're saying this proposal implies that api must be usable just like pub as in api::x?

I'll try it in the playground yet.

Yes, inside struct X(pub ::foo::Bar) there is a valid TupleField expression pub::foo::Bar.

3 Likes

I see, but is that syntax really needed? The proposal didn't mention anywhere of using api as a prefix qualifier with ::. Like, I think ::x is more useful than api::x.

::x will similiarly resolve to a crate.

IMO it'd only be useful if you could do something like self.api::some_field, which doesn't exist in Rust currently with pub.