[swift-evolution] [swift-evolution-announce] [Review #2] SE-0117: Default classes to be non-subclassable publicly

Karl razielim at gmail.com
Thu Jul 21 10:47:57 CDT 2016


> On 21 Jul 2016, at 03:41, Brent Royal-Gordon <brent at architechies.com> wrote:
> 
>> On Jul 20, 2016, at 3:26 PM, Karl <razielim at gmail.com> wrote:
>> 
>>> Sealed is *non-committal*. It makes no promises to wider scopes about whether there are other subclasses/overrides; it merely states that code outside the module may not subclass/override. `final`, on the other hand, is an *affirmative* statement: there are no subclasses/overrides and there never will be. Code outside the module is permitted to rely on that fact—for instance, it can generate static calls and conflate dynamic Self with static Self in conformances.
>> 
>> This is exactly what I'm talking about - this is actually a very simple discussion. Throwing around words like “non-committal” and “affirmative” and speaking abstractly doesn’t disguise the brunt of what you’re saying: limiting “open” to public classes only lets you be sloppy inside your own module. That’s the only reason to make it like that. If I were describing the concept of “sloppiness” while trying my hardest not to use the word itself, I would probably say pretty much what you just wrote - wanting to remain non-committal, avoid definite, affirmative statements, etc.
> 
> No, that is *not* the only reason. The other reason is library evolution.
> 
> A class that is closed in 1.0 can be opened in 1.1; a class that is `final` in 1.0 *cannot* be opened in 1.1 (or at least it's a breaking change if it is). This is a really important distinction for binary compatibility—but binary compatibility only matters at the public boundary. An `internal`-or-less class does not have to worry about maintaining a compatible binary interface between different versions, but a `public` class does.
> 
> When I say it's "non-commital", that's what I mean: It does not commit future versions of the module to either permit or forbid subclasses. A future version can either make it `open` or `final` as the module's evolving design demands. A `final` class must remain `final` forever. It's the difference between a door that's locked and a door that's bricked up.
> 
> Yes, it is *also* true that sealed-by-default permits sloppiness as long as it's contained to internal scope. But that's quite typical of Swift. It's why the default access control is `internal`, not `private` (the minimally-sloppy solution) or `public` (the minimally-bureaucratic solution). But these are two independent arguments for sealed. Railing against internal sloppiness doesn't change the library evolution argument.
> 
> -- 
> Brent Royal-Gordon
> Architechies
> 

I would be okay with the inferred situation for classes being a semantic “final”. That is, that they cannot be subclassed, but they won’t be automatically optimised in a fragile way either. We could call it “sealed” if you want to explicitly specify it - the difference is that it doesn’t only apply at the module boundary, and it’s just an annotation for the external modules’ type-checker that it shouldn’t allow this - it’s not license for the internal module’s compiler to give up flexibility. So:

public class Foo {}                        // Implicitly “sealed”. Cannot be subclassed anywhere. Does not provide optimiser guarantees of “final”.
public(sealed) class Foo {}           // as above

public final class Foo {}                // Implicitly “sealed”. Cannot be subclassed anywhere. Allows resilience-breaking optimisations.
public(sealed) final class Foo {}   // as above

public internal(open) class Foo {}                       // “open” overrides “sealed” for the internal scope. Cannot be subclassed externally; may be subclassed internally. Does not provide optimiser guarantees of “final”.
public(sealed) internal(open) class Foo {}          // as above

public(sealed) internal(open) final class Foo {}  // Error: A class cannot be both open and final

I believe that would meet the goals of:

- Not allowing subclassing from external modules unless explicitly allowed (the original goal)
- Making classes which are internally-subclassed easier to locally reason about (my nice-to-have)
- Maintain binary compatibility
- Do not give up binary flexibility unless the user explicitly asks for it (in the LibraryEvolution docs)

Is there anything I missed?

Karl
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160721/ad78b73c/attachment.html>


More information about the swift-evolution mailing list