SE-0386: `package` access modifier

I think that @_spi attribute is enough for manipulation with API access. Yeah, for Open Source projects you can't guarantee, that user will not use your "secret" name for @_spi attribute in his project, but it's ok for open source, I think :)
Of course package can grant you, thats your API will closed from your package on compile time and nobody can't change that, but we really need this?

-1 for me

I've! I want to store helper packages outside from my Sources folder and configure them separately :) Sometimes I look into Firebase repository and their Package.swift is a huge boy.

As a maintainer of multiple open-source projects in the past, I can say that this is certainly not "ok" and causes multiple issues and unneeded maintenance burden. I don't see anything positive in users importing symbols that were meant to be internal to a package by accident, relying on them without maintainers being able to prevent it, and then reporting bugs if they find something's broken due to violated SPI assumptions.

This proposal doesn't suggest removal of @_spi (as an underscored attribute it can be removed without a proposal anyway) and is not meant to be a replacement for @_spi. But if you're not interested in using package for your specific use case, I don't think it's fair to dismiss other cases where it's clearly needed.

I don't think there's anything that prevents Firebase from creating multiple packages in their single repository and adding dependencies between them through local relative paths. AFAIR SwiftPM supported it right since the beginning if not since some of the earlier Swift versions.

4 Likes

Local relative paths are illegal for a tagged version (because of their ability to escape the package). So you can do this with embedded samples that depend on the root package (albeit escaping swift build), but you cannot have the root package depend on something embedded unless the package never tags any versions and is only used in other ways.

3 Likes

I think there's merit in this grammatical criticism too, but packageprivate is a mouthful and it's strange to have internal between it and private.

How about packagewide? The -wide suffix is standard English, meaning "throughout an area".

public struct MainEngine {
    ...
    packagewide func run() { ...  }
}

(Aside: Digging through the archives, it looks like I preferred filewide over fileprivate when that keyword was being chosen. Some things never change...)

I think I'd prefer the parameterized version. The two attributes are extremely similar—unifying them allows us to teach one concept instead of two.

My first thought was that this was backwards from e.g. private(set), where the access modifier appears before the argument list, not inside it. But in fact I think this isn't true. Attributes come before modifiers, and modifiers come before declaration keywords, so there is a consistent rule: the keyword that would normally come later is the argument.

// `set` would come after `private`, so it's `private(set)`:
public private(set) var x: Int

// `package` would come after `@usableFromInline`, so it's `@usableFromInline(package)`:
@usableFromInline(package) internal var y: Int
8 Likes

Overall Iā€˜m +1 on this proposal. But the very sentence in above quote proves in my opinion why ā€œpackageā€ isn’t the right naming. A helper function ā€œisā€ package? That sentence doesn’t make any sense. But a function ā€œisā€ private or public or open or internal. Makes totally sense!

As already suggested in the pitch, I prefer one of:

  1. packageinternal (clearest, but a bit long)
  2. packagewide (clear & short enough)
  3. widened (compared to to the default internal, the scope is "widened")

A function ā€œisā€ packagewide? Works again.

4 Likes

I just asked ChatGPT for inspiration, here are some more alternatives to consider:

I like variants 1, 2, 3, and 8.

[Full chat history:

  1. What could a new access modifier for Swift be called that makes things visible to all code within the same Swift package? (Answer: package)
  2. ā€œpackageā€ is a noun and doesn’t work in sentences like ā€œthis function is packageā€ because an adjective would be expected there, like in ā€œthis package is privateā€. What could the name be as an adjective? (Answer: packageprivate)
  3. Give me 10 more alternatives. (Answer: see image)]

Has it been considered to brand this keyword as external? It definitely resembles that type of visibility, but it can still be lower than public.

public > external > internal > private
^ everywhere 
         ^ package
                    ^ module
                               ^ scope (but should really be on type level)

Or even possibly as central?

5 Likes

+1 for external as a possible alternative to package, especially as it mirrors internal. widened feels very out of place and weird to me.

7 Likes

I dunno man, once we start letting AI design the programming languages, Skynet can't be that far off. :stuck_out_tongue:

3 Likes

I don't think abbreviations like "pkg" are a good fit for the language (might even be an official antigoal).
If yet another level is really necessary (I feel no such need), this would be a good moment to reconsider the whole concept of access control, which has changed quite a lot from the simple beginning (same file < same module < everywhere).

4 Likes

It is a valid point that package is a bit different than open, public, internal, fileprivate and private, but I would say "this function is package" is not the only way of addressing a function's accessibility.
I believe you could say:

  • "This function has open accessibility"
  • "This function has public accessibility"
  • "This function has package accessibility"
    ...
  • "This function has private accessibility"

In this case, package is not so confusing. I would also say, it is the closest word to what it entails, inside a "swift package".

2 Likes

I would love to hear more opinions on the proposed keyword but branded as external before this review comes to an end.

internal is "inside a module", while external is "outside the module, but not fully public" and therefore the only place it can exist in, is inside a package. One could think that external implies public, but I don't think this is generally true. That's why this seems to be the perfect fit for this proposal if you'd ask me.

2 Likes

I'm -1 for external as it is not mutually exclusive with public and open. To me, everything that is not internal is considered external, and that would make it more confusing than package.

But I like the idea of using the same naming that we already have and their antonym. In that case I feel happier with closed rather than external, which is opposing open.

open > public > closed > internal > fileprivate > private

A type that is open, you can see it as an outsider (or subclass in case of classes)
A type that is public, you can only see it as an outsider.
A type that is closed, you can not see it as an outsider, only trusted few can.

It also resembles closeness of modules/packages together. And IMHO it is kinda mutually exclusive with internal and private, as it just describes that it is not accessible from outside world, but yet again, I think it's just preference, technically same argument can be used for closed.

6 Likes

I appreciate your feedback. Re "closed": While this looks fine on the spectrum of linear access modifiers, it's very much confusing in code. In fact public does already imply closed behavior in the context of disallowing to subclass from a public class. I wish this would have been done for protocols where a public protocol would mean that it's visible and you can use it algorithmically, but you cannot conform to it ("closed protocol"). In the flip side an open protocol would permit conformance to that protocol from outside. Unfortunately that ship sailed with the introduction of open. :slightly_frowning_face:


All this just shows that the whole access modifier topic is is very inconvenient from many perspectives. I really wish we could finally overhaul it once and for all with the Swift 6 language mode and keep the old behavior only in previous language modes.

There's so much that this can enable in the context of library development, but right now we're just stuck in front of a dead end.

2 Likes

Just to comment my thoughts on this for other review readers.

As I mentioned above, I don't think "external" is generally equals "public". Think about it from a different perspective. If you're working in a small team (that's your "module") inside a company (that's your "package") and you need to make your project available "externally". This does not imply that the project will be available outside of the company itself (that would be "public"). It's only made available externally for other teams to use (so it remains within the realm of a "package").

2 Likes

One could say that, but how often does anybody actually do so? It’s not a coincidence that the existing accessibility modifiers are adjectives.

6 Likes

Iā€˜m +0.5 on ā€œexternalā€: It’s better than ā€œpackageā€ and when compared to ā€œinternalā€ which is already scoped as ā€œinternal to this moduleā€, it makes sense as it means ā€œexternal to this moduleā€, which is correct. But I’m not 100% into it, because even ā€œexternal to this packageā€ doesn’t specify where the accessibility ends, and that’s the most important aspect of an access modifier IMO.

Here’s another naming idea which is pretty close to the original name but also contains a similar notion to ā€œinternalā€: inpackage.

Yes, ā€œinpackageā€ isn’t a real word and it could be written as ā€œinPackageā€ to be more clear about the word boundaries, but we already have ā€œfileprivateā€ which also isn’t a word and it also doesn’t use camel casing.

What do you think?

2 Likes

How about public(package)?

public(package) var example: Int

public(package) private(set) var example: Int

public(package) internal(set) var example: Int

public public(package, set) var example: Int

This would fit with the suggestion from the future directions section to use open(package) to restrict subclassing to package modules.

 // Visible inside package, subclassable inside package
open(package) class Example {} 

// Visible outside of package, subclassable inside package
public open(package) class Example {}

5 Likes

I see this:

as continued evidence that package is not the right name, even if adding a keyword for this exact access control setting is a good idea.

2 Likes