[swift-evolution] 'Public' class visibility specifiers

Slava Pestov spestov at apple.com
Tue Feb 21 03:28:39 CST 2017


> On Feb 20, 2017, at 8:47 AM, Joanna Carter via swift-evolution <swift-evolution at swift.org> wrote:
> 
> OK, here comes the girl with the big wooden spoon to stir things up a bit ;-)
> 
> When it comes to visibilities on classes, why, oh why do we have public vs open *as well as*
> the option of marking stuff as final?
> 
> Surely, outside the module boundary :
> 
> 1. public is the same as final ; i.e. you can see it but you can't derive from/override it

There is one important difference, but it is rather obscure. ‘final’ allows a class to conform to protocols where ’Self’ appears in invariant position in requirements. For example, say you have the following:

struct G<T> {}

protocol P {
  func foo(_: G<Self>)
}

class C {} // either inside your module, or elsewhere

The following is not allowed, and produces an error:

extension C : P {
  func foo(_: G<C>) {} // ‘Self’ appears in non-parameter, non-result position
}

The reason being that if you have a subclass D of C, the signature of foo() no longer matches the requirement — the caller expects to pass in a G<D>, not a G<C>. Recall that D < C does not imply G<D> < G<C> in Swift.

Note that even if ‘C’ is public and not open, we cannot allow the above conformance, because the module that defined ‘C’ might later on add a new subclass, invalidating the conformance.

If ‘C’ is final, this is OK though — we know there will be no other subclasses, so ‘Self’ and ‘C’ are indeed interchangeable.

Also worth noting that removing ‘final’ from a class is going to be an ABI breaking change (and source compatibility too), whereas changing a ‘public’ class to ‘open’ poses no such difficulty.

I might be in favor of a proposal to just remove ‘final’ altogether, though, leaving us with just open and public. I’m not sure how much the ability for classes to conform to such protocols matters in practice.

Slava

> 
> 2. open is the same as public without final ; you can see it and derive from/override it
> 
> Inside the module boundary, there is essentially no difference between public and open.
> 
> In fact, open/public is a conflation of concerns.
> 
> Both allow public visibility but, mixed in with that is restriction of inheritance. Surely public is good enough for visibility and final is good enough for inheritance restriction?
> 
> ////////////////
> // Module1 file
> 
> // MARK: base classes
> 
> open class OpenClass
> {
>  open func test() { }
> }
> 
> public class PublicClass
> {
>  public func test() { }
> 
>  public final func finalTest() { }
> }
> 
> public final class FinalPublicClass
> {
>  public final func test() { }
> }
> 
> // MARK: derived internal classes
> 
> class FrameworkDerivedPublicClass : PublicClass
> {
>  override func test() { }
> 
>  override func finalTest() { } // error : instance method overrides a 'final' instance method
> }
> 
> class FrameworkDerivedFinalPublicClass : FinalPublicClass // error : inheritance from a final class
> {
>  override func test() { } // error : instance method overrides a 'final' instance method
> }
> ///////////////
> 
> ///////////////
> // Module2 file
> 
> class OpenSubclass : OpenClass
> {
>  override func test() { }
> }
> 
> class PublicSubclass : PublicClass // error : cannot inherit from non-open class 'PublicClass' outside of its defining module
> {
>  override func test() { } // error : overriding non-open instance method outside of its defining module
> 
>  override func finalTest() { } // error : method does not override any method from its superclass
> }
> 
> class FinalPublicSubclass : FinalPublicClass // error : cannot inherit from non-open class 'FinalPublicClass' outside of its defining module
> {
>  override func test() { } // error : instance method overrides a 'final' instance method
>                                           // error : overriding non-open instance method outside of its defining module
> }
> ////////////////
> 
> In fact, the test() method in FinalPublicSubclass gives two errors one of which is the same as when declared in FrameworkDerivedFinalPublicClass.
> 
> If final is good enough for inside the module boundary, and the same "overriding final method" error appears in both places, do we really need this added complexity?
> 
> Surely, if we take public as meaning no inheritance control anywhere :
> 
> public class BaseClass
> {
>  public func test() { }
> 
>  public final func finalTest() { }
> }
> 
> And then, either in or out of the module :
> 
> class DerivedClass : BaseClass
> {
>  override func test() { }
> 
>  override func finalTest() { } // error : instance method overrides a 'final' instance method
> }
> 
> Or, if BaseClass were marked as final, then inheritance of the whole class is prohibited.
> 
> So, I am proposing a reduction in keywords from open, public and final, to just public and final.
> 
> --
> Joanna Carter
> Carter Consulting
> 
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution



More information about the swift-evolution mailing list