[swift-evolution] Final by default for classes and methods

Kevin Ballard kevin at sb.org
Mon Dec 21 12:29:36 CST 2015

I was on the fence initially, but you've sold me here. +1 for sealed-by-

-Kevin Ballard

On Sat, Dec 19, 2015, at 07:21 PM, Jordan Rose via swift-evolution wrote:
>> On Dec 7, 2015, at 20:30 , John McCall via swift-evolution <swift-
>> evolution at swift.org> wrote:
>>> On Dec 7, 2015, at 7:18 PM, Matthew Johnson via swift-evolution <swift-
>>> evolution at swift.org> wrote:
>>>> Defaults of public sealed/final classes and final methods on a
>>>> class by default are a tougher call. Either way you may have design
>>>> issues go unnoticed until someone needs to subclass to get the
>>>> behavior they want. So when you reach that point, should the system
>>>> error on the side of rigid safety or dangerous flexibility?
>>> This is a nice summary of the tradeoff.  I strongly prefer safety
>>> myself and I believe the preference for safety fits well with the
>>> overall direction of Swift.  If a library author discovers a design
>>> oversight and later decides they should have allowed for additional
>>> flexibility it is straightforward to allow for this without breaking
>>> existing client code.
>>> Many of the examples cited in argument against final by default have
>>> to do with working around library or framework bugs.  I understand
>>> the motivation to preserve this flexibility bur don't believe bug
>>> workarounds are a good way to make language design decisions. I also
>>> believe use of subclasses and overrides in ways the library author
>>> may not have intended to is a fragile technique that is likely to
>>> eventually cause as many problems as it solves.  I have been
>>> programming a long time and have never run into a case where this
>>> technique was the only way or even the best way to accomplish the
>>> task at hand.
>>> One additional motivation for making final the default that has not
>>> been discussed yet is the drive towards making Swift a protocol
>>> oriented language.  IMO protocols should be the first tool
>>> considered when dynamic polymorphism is necessary.  Inheritance
>>> should be reserved for cases where other approaches won't work (and
>>> we should seek to reduce the number of problems where that is the
>>> case).  Making final the default for classes and methods would
>>> provide a subtle (or maybe not so subtle) hint in this direction.
>>> I know the Swift team at Apple put a lot of thought into the
>>> defaults in Swift.  I agree with most of them. Enabling subclassing
>>> and overriding by default is the one case where I think a
>>> significant mistake was made.
>> Our current intent is that public subclassing and overriding will
>> be locked down by default, but internal subclassing and overriding
>> will not be.  I believe that this strikes the right balance, and
>> moreover that it is consistent with the general language approach
>> to code evolution, which is to promote “consequence-free” rapid
>> development by:
>> (1) avoiding artificial bookkeeping obstacles while you’re hacking up
>> the initial implementation of a module, but
>> (2) not letting that initial implementation make implicit source and
>> binary compatibility promises to code outside of the module and
>> (3) providing good language tools for incrementally building those
>> initial prototype interfaces into stronger internal abstractions.
>> All the hard limitations in the defaults are tied to the module
>> boundary because we assume that it’s straightforward to fix any
>> problems within the module if/when you decided you made a mistake
>> earlier.
>> So, okay, a class is subclassable by default, and it wasn’t really
>> designed for that, and now there are subclasses in the module which
>> are causing problems.  As long as nobody's changed the default (which
>> they could have done carelessly in either case, but are much less
>> likely to do if it’s only necessary to make an external subclass),
>> all of those subclasses will still be within the module, and you
>> still have free rein to correct that initial design mistake.
> I think John summarized my position very well, so of course I'm going
> to come in here and add more stuff. :-)
> In working on the design for library evolution support ("resilience"),
> we've come across a number of cases of "should a library author be
> able to change this when they release v2 of their library?" Many
> times, the answer is it's *possible* to do something in one direction,
> but not at all safe to go the other way. For example, you can always
> add public methods to a class, but you can't *remove* public methods
> because you don't know who's calling them. You can mark them
> deprecated, but that doesn't help with any client apps that have
> already been compiled and shipped.
> One of the things that came up was "can you add 'final' to a class?"
> And of course you can't, because you don't know who may have already
> subclassed it. That's very unfortunate for a library author who simply
> forgot to add 'final' when they were first writing the class.
> The interesting thing about this is that the "error of omission"—of
> failing to think about whether a class should be final—is worse than
> the alternative. Ignoring optimizations for a minute, a class that
> *starts out* 'final' can certainly become non-final later; it doesn't
> change how the class is currently used.* For a lot of library
> evolution questions, this is the preferred answer: *the default should
> be safe,* and the designer of the class can choose to be more
> aggressive later.
> This is also the guiding principle behind the behavior of 'public'. A
> number of people have asked for members of a public struct to
> implicitly be made public. But here again the "error of omission" is
> problematic: a helper function you add for your own use may now be
> depended on by client apps far and wide, just because you forgot to
> customize the access control. So Swift says you should explicitly
> consider the public interface of every type.
> Why 'sealed' instead of 'final' as the default? Because inheritance is
> useful, and within your *own* code having to opt into it starts to
> feel like unnecessary clutter. This is a trade-off, just like
> defaulting to 'internal' over 'private', but it's one that keeps life
> easy for a single developer with a single module: their app. (And the
> compiler can still do useful things with non-final classes if it can
> see the entire class hierarchy.) Additionally, limiting inheritance to
> the *current* file (a la 'private') is also potentially useful.
> This direction separates "limiting inheritance/overrides" from "has no
> subclasses/overrides". The former is about defining the limits of your
> API; the latter is a promise that can be used for performance. I think
> that's a good thing.
> Jordan
> * Even without optimizations a 'final' class cannot safely drop the
>   'final'. If a class is 'final', it may have additional 'required'
>   initializers added in extensions.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20151221/1bca4405/attachment.html>

More information about the swift-evolution mailing list