Swift protocols currently have no way to express requirements that must be implemented by conforming types but are not intended to be part of the public API of those types.
As a result, protocol authors often have to expose methods or properties as public or internal purely to satisfy protocol conformance, even when those members are implementation details used only by protocol extensions.
This pitch explores the idea of non-public protocol requirements: requirements that must be implemented by conforming types, but are callable only from the protocol itself and its extensions, and are not visible when the protocol is used as a type.
let concrete = DebugCache(...)
concrete.loadValue(forKey: "a") // âś… visible on the concrete type
let cache: Cache = concrete
cache.loadValue(forKey: "a") // ❌ still not visible via the protocol
The key ideas are:
impl requirements must be implemented by conforming types.
The access level of the implementation is independent from the requirement.
Visibility depends on how the value is typed:
visible on the concrete type if declared public,
never visible when accessed through the protocol type.
This allows protocols to define layered contracts and safer default behavior without relying on inheritance or exposing unnecessary API surface.
The exact spelling (impl above) and detailed semantics are intentionally left open for discussion.
I think if you want to further the discussion, we need more than a vague "wouldn't it be nice". We (mostly) all agree it would be nice, what's missing is the work on an actual proposal and implementation
Non-public protocol requirements can definitely be distinguished from sealed protocols, but I agree with other people in the thread that just asking an LLM to write your post and all the responses is not really respectful of other people’s time and ideas.
Hello guys, what is your point?
I really use an LLM to answer the comment and actually I also use LLM to generate the pitch.
I am not an English native speak and I believe that my English is not good enough to answer some comments properly. But everything I post are my ideas. I prompt my ideas in my language and asked for a text in English.
Sorry if someone felt that I was not respectful. It is not easy to write to me and explain some technical ideas is even harder. I think you should focus more in the content than in the form.
I am a Swift developer since the beginning and I just want to contribute to the language. I think Swift is a great language however there are two or three point that it is hard to understand why was not implemented yet. And this is one of that. I see a lot of focus in very complex things that we hardly ever need to use, but simple things like that are completely forgotten.
I spent some time to write this post with a translator assisting me. I hope this isn't a problem too.
Using machine translation is fine, as is using an LLM to help you find the right words to express your own thoughts. You are also welcome to just post in your native language and expect other people to machine-translate your posts themselves.
The problem with your post above is that it comes across as you asking an LLM to make a reply without bothering to read or understand the linked discussions. This is inherently a little disrespectful of other people’s time, both because you’re signaling a lack of interest in talking to the people who are trying to engage with your idea, and because LLMs do not generate new ideas and so are not actually developing the conversation in any meaningful way.
If you had tried to read and engage with the linked threads, you would probably understand that non-public protocol requirements are closely related to sealed protocols because only a type within the protocol’s module would be able to implement that requirement. That is, a non-defaulted internal requirement would inherently make the protocol sealed. So reading the previous discussion of sealed protocols is directly relevant.
I am really not understanding why you are saying this. I read all the discussions on the links. And I think that is totally different proposals
Why?
My proposal is not to make a requirement a private individual. The idea is: This requirement needs to be implemented like any other requirement; however, it does not necessarily have to be part of the concrete type—it is optional.
If you are manipulating the abstract type (protocol), the requirement could not be called.
I really can't understand what this has to do with sealed protocols. Maybe I choose a wrong title. Maybe could be: "implementation-only protocol requirement"
I think I understand what you’re trying to achieve:
“This protocol enables certain algorithms based on a helper method. The algorithms should be API on conforming types, but the helper method should not. The helper method should only be available within the implementations of algorithms on the protocol.”
The idea has some merit, but it also needs refinement.
Notably, a protocol has no business insisting that conforming types must not have some function in their API. At most, the protocol could choose not to require that a function be API. It would then be a decision for the conforming types to make.
protocol P { impl func fooHelper() }
extension P { func foo() { fooHelper() } }
struct S: P { func fooHelper() {} }
struct T: P { impl func fooHelper() {} }
S().foo() // okay
T().foo() // okay
S().fooHelper() // okay
T().fooHelper() // error: fooHelper is an implementation detail
FWIW, there is an aspect to private protocol requirements that's separate from sealed protocols, and one I've wished for myself before on multiple occasions: the ability to implement generic functionality in an extension to the protocol itself, in terms of its own (private) requirements; e.g., default implementations for common properties/methods in terms of private requirements. From a similar previous thread:
Agreed — and in my mind, it'd be enough for a hypothetical impl func/impl var to mean "this is accessible to extensions of the protocol (potentially in its defining module only) regardless of the actual access control specified on conforming types themselves". Conforming types could then choose their own access control as they see fit (making these private requirements accessible in other contexts, or not), but the protocol is able to rely on the requirement as an implementation detail.
The only difference is that I don't know if it would allow the use of "impl" within the definition of a concrete type. I was thinking more about it being allowed only within a protocol. Within the type, it could use any visibility modifier it deemed appropriate. But that's just an implementation detail, which could be discussed further later.
Exactly, I also experience several situations where I feel the lack of a feature like this.
It's possible to work around it, but in the end I feel that the implementation wasn't good enough.
The example you gave in that post was perfect for demonstrating the problem. And it could be solved in the following way:
/// Conforming types keep a tally of some type of event.
protocol Tallying {
impl var totalTally: Int
/// Records that the associated event has occurred by incrementing `totalTally`.
mutating func tally()
}
extension Tallying {
mutating func tally() {
totalTally += 1 // âś… It works, because all subclasses need to implement the get and set methods.
}
}
struct A: Tallying {
private(set) var totalTally: Int
}
Another simple example:
protocol StringValidator {
func validate(_ input: String) -> Bool
/// Mandatory implementation requirement, but not part of the public API.
impl func performValidation(_ input: String) -> Bool
}
extension StringValidator {
func validate(_ input: String) -> Bool {
trimmingCharacters(in: .whitespacesAndNewlines).isEmpty && performValidation(input)
}
}
struct EmailValidator: StringValidator {
//It can be private, internal, or public.
//The contract is satisfied regardless of that.
private func performValidation(_ input: String) -> Bool {
input.contains("@") && input.contains(".")
}
}
let validator: StringValidator = EmailValidator()
validator.validate("a@b.com") // OK
validator.performValidation("x") // ❌ Never works
let emailValidator = EmailValidator()
emailValidator.validate("a@b.com") // OK
emailValidator.performValidation("x") // ❌ It doesn't work because "EmailValidator" was implemented as private.
let debugValidator = DebugValidator()
debugValidator.validate("a@b.com") // OK
debugValidator.performValidation("x") // Fine
Effectively, yes, without trying to bring up the rest of typeprivate's baggage (I do still long for typeprivate for similar reasons, but that's been litigated to death and back again.)
Yeah, my critique boils down to the same one I have for "type private" as a whole.
Which is to say, there are clearly use cases (even in the stdlib—e.g., description) just as there are in other languages. But it's frustrated by one of Swift's tentpole features being that extensions are allowed for any type (or, equally and for that reason this same critique being applicable here, protocols) from anywhere.
Something that's "type private" in Swift is really public-but-with-an-annoying-step, for the reason that I can trivially write a trampoline extension that wraps the "type private" entrypoint—this is straightforward enough I suppose one could even create a macro that does just this whenever and wherever. It would then differ from, say, using the underscore convention or just writing a polite note in the documentation only in terms of easily silenced compiler-enforced nagging.
Perhaps an availability-like attribute that triggers something akin to a deprecation warning when an API is called from outside an extension would be appropriate, but giving this feature a spelling like an access modifier when it doesn't meaningfully control access is misleading.
IMO, there's a nuanced difference between how this applies to protocols vs. typeprivate in general, though — the primary purpose of what's being pitched here isn't really "positive" access control (trying to explicitly protect members which could otherwise be accessed), but enabling a protocol author to rely on basic functionality on a type without also forcing the type to expose that functionality elsewhere ("negative" access control, maybe?).
If impl requirements on a protocol can only be used within extensions in the same module that the protocol is defined in (i.e., reserving that functionality for the protocol author), I'd say you avoid most of the concerns of post-hoc exposure of otherwise-private members.
ETA: And, I mean, access control is not a security barrier — the feature is the compiler-enforced nagging allowing us to express intent and keeping us from making mistakes, not somehow truly barring access. So in this case, I would like the nagging over leaving polite notes or using underscores
Now types that conform to FooImpl only need to implement the fooHelper method, and they can do so at any visibility fileprivate or above:
public struct S: FooImpl {
fileprivate func fooHelper() { print("S.foo") }
}
public struct T: FooImpl {
public func fooHelper() { print("T.foo") }
}
Outside of this one file, only the conformance to Foo is visible, because FooImpl is private (it would also be possible to make it internal if you prefer).
Of course, this does not enforce that types conforming to Foo must also conform to FooImpl, and indeed it would be technically possible for any type anywhere to conform to Foo directly.
But if the goal is simply to allow conformance to Foo within a single file or module, without spilling the implementation details beyond those borders, then this strategy achieves that.