TLDR: Should we relax the access restriction for satisfying protocol requirements, at the cost of a small bit of indirection? Jordan reluctantly votes yes.
Back in Swift 1, we had a rule that for a method to satisfy a protocol requirement, it must be at least as visible as either the protocol or the conforming type:
public
type conforming tointernal
protocol: method must be at leastinternal
internal
type conforming topublic
protocol: method must be at leastinternal
public
type conforming topublic
protocol: method must bepublic
The rationale here was that the method effectively was public, since you could always access it through the protocol.
In Swift 2, however, along came protocol extensions, and suddenly people (particularly @dabrahams) wanted to do things like this:
public protocol ImportantProto {
func difficultRequirement() -> String
}
internal protocol SpecialCaseImplementationDetail {
func muchSimplerRequirement() -> Int
}
extension SpecialCaseImplementationDetail {
/*!*/ func difficultRequirement() -> String {
return "\(muchSimplerRequirement())"
}
}
public struct ConcreteTypeA: ImportantProto, SpecialCaseImplementationDetail {
internal func muchSimplerRequirement() -> Int { … }
}
public struct ConcreteTypeB: ImportantProto, SpecialCaseImplementationDetail {
internal func muchSimplerRequirement() -> Int { … }
}
That is, share the implementation of ImportantProto across multiple types that happen to have a common implementation, but not expose that common implementation. The problem? The default implementation of difficultRequirement
isn't public
. It can't be, since it depends on the internal
protocol SpecialCaseImplementationDetail.
In Swift 3, SE-0025 came along and included this particular clause:
The compiler should not warn when a broader level of access control is used within a type with more restrictive access, such as
internal
within aprivate
type. This allows the designer of the type to select the access they would use were they to make the type more widely accessible. (The members still cannot be accessed outside the enclosing lexical scope because the type itself is still restricted, i.e. outside code will never encounter a value of that type.)
Whether or not this is a good thing isn't something to discuss right now. The important point is that there were bugs in the implementation. If you mark the default implementation of difficultRequirement
above as public
…the code compiles. Despite the method not actually being public. Uh oh!
Did it always work? No, this very often resulted in linker errors. But it worked in enough cases that we couldn't just add a diagnostic in Swift 4.2. So @Slava_Pestov put in a practical fix: if the user wrote this, they wanted it to work. Therefore, count the requirement as satisfied, but require any clients use dynamic dispatch when calling the method through the protocol (no "devirtualization" optimization).
At this point, though, we've got a public
that SE-0025 says is supposed to have no effect, but which does actually change compilation. And we also now have a reason to allow a distinction between "is public
" and "satisfies a public requirement": library evolution. That is, if the requirement is public but my implementation is not, then it's perfectly okay to remove my implementation later and fall back to the default. If the implementing method is public
, that's not an option, because clients could be using it directly.
(As with all library evolution / "resilience" features, this only applies to the standard library and overlays at this time—code planned to be shipped by Apple within our OSs, rather than bundled with an app.)
So, what do people think of removing the access check for satisfying protocol requirements? When the compiler encounters such a scenario, it will make sure any calls to that requirement use dynamic dispatch if they aren't able to access the implementation directly. That handles both the "shared implementation" pattern shown above and the newer "library evolution" enhancement, and it gets us back to consistency with SE-0025 while still having an explicit declaration of intent: the conformance to the public protocol.
P.S. I stuck this in Discussion because I'm not sure it needs a full-on proposal. Once I've gotten some feedback, I'll ask a core team member.