[Review] SE-0117: Default classes to be non-subclassable publicly

This proposal is specifically targeting library interfaces. It's not a slippery slope towards locking things down at a sub-library level because (1) we've already considered and rejected the analogous sub-library-level proposal (as "final by default") and (2) almost all of the justifications are tied to the specific problems that arise with library interfaces.

In general, Swift already treats library interfaces as a special point at which a lot of considerations change. The most prominent example of that is access control, but there are a lot of other examples that will become more apparent as we complete the resilience model, solidify the ABI, and start really supporting binary distribution of libraries. The basic idea is that library designers face a set of language problems that other programmers don't, and the language ought to understand that and provide a different set of features and defaults. This is a language design that's made possible by Swift's relatively strong commitment to defining and enforcing library boundaries, as opposed to the languages you list. To me, this is a great way to only get dogmatism when it's actually useful and necessary.

John.

···

On Jul 6, 2016, at 1:41 PM, Goffredo Marocchi via swift-evolution <swift-evolution@swift.org> wrote:
Sent from my iPhone

On 6 Jul 2016, at 21:22, Paul Cantrell via swift-evolution <swift-evolution@swift.org> wrote:

In the era of increased open sourcing, easy forking, and more community-driven development, this concern is less severe than it used to be. I rarely use any closed-sourced libraries for iOS development. If I need to tweak some library and non-subclassibility is getting in the way, then I can fork it — and perhaps even contribute my changes back to improve the upstream project. In an open source world, “closed by default” makes a lot more sense.

Maintaining a fork, realistically often without hope of upstream merging, your changes is feels like a very business unfriendly idea and less scalable than it sounds in many environments.

I see closed by default as part of the movement some people seem to be embracing of opt-out model in which freedom and versatility is forcefully restrained, making the language more complex and exotic/breaking conventions for the sake of protecting people from themselves, instead of opt-in models which require programmers to be diligent and know when to constrain themselves while enjoying more flexible defaults.
I am not asking for JavaScript, but I do not want this language to go the complete polar opposite, more dogmatic than C++ or Java.

It certainly has its problems, but in practice it’s no worse than brittle workarounds such as unintended subclassing.

In the case of forking, upgrading a library comes with the cost of merging changes. In the case of brittle subclassing, upgrading a library comes with the cost of your patches suddenly breaking. In either case, you have a body of code which is tightly coupled to an external library in ways that the library's author isn’t monitoring for breakage.

In my experience, forking is usually the less costly route because it gives more control. Modern version control makes maintaining a fork over time much easier than it used to be — and the ever-improving social dynamics of pull requests make forks less likely to live on forever.

Cheers,

Paul

···

On Jul 6, 2016, at 3:41 PM, Goffredo Marocchi <panajev@gmail.com> wrote:

On 6 Jul 2016, at 21:22, Paul Cantrell via swift-evolution <swift-evolution@swift.org> wrote:

In the era of increased open sourcing, easy forking, and more community-driven development, this concern is less severe than it used to be. I rarely use any closed-sourced libraries for iOS development. If I need to tweak some library and non-subclassibility is getting in the way, then I can fork it — and perhaps even contribute my changes back to improve the upstream project. In an open source world, “closed by default” makes a lot more sense.

Maintaining a fork, realistically often without hope of upstream merging, your changes is feels like a very business unfriendly idea and less scalable than it sounds in many environments.

Intention.

IMO, intention may lead to more secure systems (and libraries). By
having to explicitly final everything I have to choose with parts of
my class/library would be locked and have to worry and check if any
public thing could be used to exploit it or make the system work in a
way I did not intended to. Also, final will prevent anyone including
me from extending/overriding. Defaulting to final would require from
me to explicitly declare the open endpoints in my libraries, so I
could explicitly open only the ones that are really intended to be
used in that way by third-parties.

As an example, I'm working on a system which has an internal
representation of Files and Folders using a common superclass (lets
call it Entry). I would like for other developers to be able to create
new entry types (not only inheriting from File) but I do not wish for
them to extend from Folder or any of its subclasses (specialised
folders). By using final I can prevent others from extending my Folder
but I cannot extend it myself and thus need another mechanism for
achieving this behaviour without bloating the Folder class with all
its specialisations. This proposal would allow me to make my Folder
and its subclasses publicly available/usable but would prevent others
from subclassing and thus misusing them in ways I did not intend them
to. The same rationale applies to methods.

L

···

On 6 July 2016 at 16:09, Goffredo Marocchi <panajev@gmail.com> wrote:

Leonardo, how is defaulting to final/sealed helping you write better libraries than having a final keyword for what you need to close instead?

Sent from my iPhone

On 6 Jul 2016, at 16:48, Leonardo Pessoa via swift-evolution <swift-evolution@swift.org> wrote:

The review of "SE-0117: Default classes to be non-subclassable publicly" begins now and runs through July 11. The proposal is available here:

       https://github.com/apple/swift-evolution/blob/master/proposals/0117-non-public-subclassable-by-default.md

       * What is your evaluation of the proposal?

+1. Being able to control how a class from my libraries are going to
be used by third-parties could enable a better designed API with more
control of how it is intended to be used. I'm just not fond of using
the proposed keywords ('subclassable' and 'overridable') as they feel
more like protocol or attribute names; I'd be more inclined to use the
alternative 'public open' instead, or 'public(open)' as a second
option.

       * Is the problem being addressed significant enough to warrant a change to Swift?

I'd say it is significant to every language.

       * Does this proposal fit well with the feel and direction of Swift?

Yes.

       * If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

C# uses the keyword 'virtual' to explicitly mark methods that can be
overriden (not considered in the alternatives but I'm not a big fan of
it).

       * How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

I've took (a small) part on the thread discussing this proposal but
followed it closely
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Scott, I think your writing got a bit confuse but, if I got your
intention right, since you are the owner of the class, you may choose
to subclass it or not internally, no questions asked. I need no finals
in my apps and I only subclass if I intend to. If you are in control
of your own code, why would you need to ensure there would be no
subclassing/overriding? I'm not opposed to keeping the keyword if it
is important to anyone to be sure of that for internals. As for what
is public you will gain better control of what other people do to your
classes and methods.

L

···

On 6 July 2016 at 16:56, Scott James Remnant <scott@netsplit.com> wrote:

On Jul 6, 2016, at 12:50 PM, Leonardo Pessoa <me@lmpessoa.com> wrote:

Scott, you really got a point here: should this proposal pass, I
believe the final keyword should be removed as it would be already the
default behaviour and thus unnecessary. I don't think this is on the
proposal.

Removing the `final` keyword would mean there would be no way to have a class of `internal` (default) scope that subclasses and another class of `internal` (default) scope and overrides a method of `internal` (default) scope.

Scott

Strong +1 for me, separating access control from "open-to-subclasses" is
a great feature. Leonardo's example of why final is not enough is great.

My stance is that Swift should be safe by default, predictable, and the
compiler should know enough about the code to actually help me and this
proposal fits this.

About testability, I don't think we should downplay how to test "sealed"
classes from outside the framework. I do agree that the class should be
treated as a black box with only public APIs available, but testing how
my app is using those APIs should be possible somehow.

Last but not least, I don't like the proposed subclassable/overridable,
specially because they imply public.

···

On Wed, Jul 6, 2016, at 12:35, Leonardo Pessoa via swift-evolution wrote:

Intention.

IMO, intention may lead to more secure systems (and libraries). By
having to explicitly final everything I have to choose with parts of
my class/library would be locked and have to worry and check if any
public thing could be used to exploit it or make the system work in a
way I did not intended to. Also, final will prevent anyone including
me from extending/overriding. Defaulting to final would require from
me to explicitly declare the open endpoints in my libraries, so I
could explicitly open only the ones that are really intended to be
used in that way by third-parties.

As an example, I'm working on a system which has an internal
representation of Files and Folders using a common superclass (lets
call it Entry). I would like for other developers to be able to create
new entry types (not only inheriting from File) but I do not wish for
them to extend from Folder or any of its subclasses (specialised
folders). By using final I can prevent others from extending my Folder
but I cannot extend it myself and thus need another mechanism for
achieving this behaviour without bloating the Folder class with all
its specialisations. This proposal would allow me to make my Folder
and its subclasses publicly available/usable but would prevent others
from subclassing and thus misusing them in ways I did not intend them
to. The same rationale applies to methods.

L

On 6 July 2016 at 16:09, Goffredo Marocchi <panajev@gmail.com> wrote:

Leonardo, how is defaulting to final/sealed helping you write better
libraries than having a final keyword for what you need to close
instead?

Sent from my iPhone

On 6 Jul 2016, at 16:48, Leonardo Pessoa via swift-evolution <swift- >> evolution@swift.org> wrote:

The review of "SE-0117: Default classes to be non-subclassable
publicly" begins now and runs through July 11. The proposal is
available here:

https://github.com/apple/swift-evolution/blob/master/proposals/0117-non-public-subclassable-by-default.md

      * What is your evaluation of the proposal?

+1. Being able to control how a class from my libraries are going to
be used by third-parties could enable a better designed API
with more
control of how it is intended to be used. I'm just not fond of using
the proposed keywords ('subclassable' and 'overridable') as
they feel
more like protocol or attribute names; I'd be more inclined to
use the
alternative 'public open' instead, or 'public(open)' as a second
option.

      * Is the problem being addressed significant enough to
        warrant a change to Swift?

I'd say it is significant to every language.

      * Does this proposal fit well with the feel and direction of
        Swift?

Yes.

      * If you have used other languages or libraries with a
        similar feature, how do you feel that this proposal
        compares to those?

C# uses the keyword 'virtual' to explicitly mark methods that can be
overriden (not considered in the alternatives but I'm not a big
fan of
it).

      * How much effort did you put into your review? A glance, a
        quick reading, or an in-depth study?

I've took (a small) part on the thread discussing this proposal but
followed it closely
_________________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_________________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Regards
LM
(From mobile)

* What is your evaluation of the proposal?

-1 as is. I do not want to be constrained by authors of libraries or
frameworks into interacting with a system in only the ways they forsee.
By making the default be non-subclassable, if a designer does not put
thought into all the ways a class can be used then I as a consumer of
the library am penalized.

Out of curiosity, what is your feeling about “internal” as the default level of access control? It seems that following your concern to its logical conclusion would lead to a design where all members of a public class would be forced to be public. After all, the author of a library or framework may not forsee the need to interact with a member that they did not explicitly mark public

Can't really help for feel like it is training wheels all around... or padlocks on every kitchen cupboards. What if this had been the philosophy from swift 0.1, what would the ecosystem look like today? (genuine question to which I do not have the answer)

···

On Jul 6, 2016, at 7:52 AM, Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:

On Jul 5, 2016, at 5:54 PM, Kevin Lundberg via swift-evolution <swift-evolution@swift.org> wrote:

-Chris
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

I can appreciate this argument, but I would hope that my viewpoint is a
bit more nuanced than that :). To turn the tables a bit, one could argue
that, taken to the proposal's logical conclusion, writable properties
should not be publicly writable for safety's sake. Instead, they should
be required to be explicitly marked as publicly settable if needed as it
should be up to the author of that type to carefully consider how safe
it is for others to mutate the state of their types. Yet today both
sides of a property are given the same access level unless one
explicitly restricts the visibility of it's setter. The current behavior
(of properties specifically, and the access control feature as a whole)
achieves a balance of convenience, power, and safety that I am happy with.

The proposed change however sacrifices too much convenience for a
greater sense of safety and control by default. It's not possible to
accidentally override a method like it is in java or objective-c, so the
proposal won't help people who might accidentally override something. If
a developer tries to override a method or class they probably have a
specific reason in mind for doing so, and taking away the options that
this ability presents, by nature of it being the default behavior,
doesn't sit well with me as it has the potential to take away a good
tool from my toolbox.

I agree that API authors should have the power to restrict this
dimension of a type's usage, but I feel that it should be a conscious
choice to do so.

- Kevin

···

On 7/6/2016 1:52 AM, Chris Lattner wrote:

On Jul 5, 2016, at 5:54 PM, Kevin Lundberg via swift-evolution <swift-evolution@swift.org> wrote:

* What is your evaluation of the proposal?

-1 as is. I do not want to be constrained by authors of libraries or
frameworks into interacting with a system in only the ways they forsee.
By making the default be non-subclassable, if a designer does not put
thought into all the ways a class can be used then I as a consumer of
the library am penalized.

Out of curiosity, what is your feeling about “internal” as the default level of access control? It seems that following your concern to its logical conclusion would lead to a design where all members of a public class would be forced to be public. After all, the author of a library or framework may not forsee the need to interact with a member that they did not explicitly mark public.

-Chris

Regards
LM
(From mobile)

If you have a ParentClass and a SubClass, and the ParentClass is sealed while the SubClass is subclassable. What happens? No matter how this question is answered, I don't like the answer. (compile error => bad. || make it as the user wishes => bad; what do we gain by letting ParentClass remain sealed? || make ParentClass implicitly subclassable too => bad.)

I'm happy that there are not only supporters for this proposal, but imho the example is no compelling argument:
With no doubt, I'd expect I can subclass only SubClass — like I can't instantiate an abstract class, which is just fine for its children.
ParentClass might do some dangerous things that don't happen in SubClass, so there might even be a use-case (but imho it would be better if I could mark ParentClass as internal in this situation).

But imho there is another aspect I haven't read about yet:
"final by default" would have had direct impact on any developer, while this proposal merely changes things for those who create libraries…
So, the question is: How will those be build?

If you live in a world of secrets and non-disclosure, I can understand that sealed is desirable — but that's not the future I want to see, and github is a good indication that others share this opinion.

If you share the sympathy for Open source, the two scenarios are as follows:
We stick with "open by default"; users of libraries will use them in ways that the creator hasn't thought of before, and bugs will show up.
But: We know how to deal with bugs, that's our job! So in the best case, we find the reason for the bad behavior, create a pull request, and everyone is happy.

With "sealed by default", the situation changes:
Users are protected from some simple bugs, but nonetheless, they'll encounter situations where the library doesn't do exactly what they want.
So, you take a look at the source, find the problem, and fix it.
It's no bug at all, it's just a tiny degree of freedom that is missing because it wasn't important to the author.
You can create a pull request as well, but it doesn't offer a real improvement to the author, who is already busy with dozens of similar requests by other users -> you end up with a custom branch with all the housekeeping associated with it.

So overall, I'm quite sure that the proposal won't improve software quality, but rather degrade it.

:slight_smile:

···

On Jul 6, 2016, at 10:39 PM, Tino Heth via swift-evolution <swift-evolution@swift.org> wrote:

Tino
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

lol :slight_smile:

···

On Thu, Jul 7, 2016 at 1:41 PM, Tino Heth via swift-evolution < swift-evolution@swift.org> wrote:

In an open source world, “closed by default” makes a lot more sense.

That sounds Orwellian to me:
War is Peace, Freedom is Slavery, Ignorance is Strength — and sealed is
open?

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

To be fair and practical with the proposal it does not remove the
ability to inherit any of the UIKit classes because all Objective-C
classes are imported as "open".

Food for thought on inheritance-is-the-only-fix, how have we worked all
these years with C libraries like Security, Foundation, Core Graphics,
GCD, and so on if C doesn't have inheritance? My point here is not to
suggest anything other than to keep an open mind, the proposal is not
removing OO from Swift. It is definitely a non conventional approach
when compared to other languages, Kotlin is the only language with
something similar but I have never used it.

I have seen a lot of problems being solved by inheritance that could be
solved by composition in a way that was more reliable, testable, and
clear. When I asked why inheritance the answer is most of the time
because that is the way they know how to do things, like it was the only
tool available. I think Swift has as opportunity to increase software
quality by providing good defaults that make developers understand what
are the consequences of their choices. Maybe I'm too optimistic.

···

On Thu, Jul 7, 2016, at 16:01, Aditya Krishnadevan via swift-evolution wrote:

I agree with everything James has to say here. Making classes non-
subclassable by default is not optimal.
A lot of fixes for small bugs in UIKit involve using a subclass that
overrides at method or slightly modified behaviour as a temporary
patch until the issue is fixed at the framework level.
Preventing this *and* introducing a new keyword simply adds to the
complexity without bringing too much to the table.

Aditya Krishnadevan.

On 06-Jul-2016, at 6:03 PM, James Campbell via swift-evolution <swift- > evolution@swift.org> wrote:

-0.5 I think preventing subclassing is a bad idea, sometimes there
are bugs which can only be resolved by subclassing and this removes a
lot of power from app makers.

On 6 July 2016 at 13:23, Jacopo Andrea Giola via swift-evolution <swift- >> evolution@swift.org> wrote:

> * What is your evaluation of the proposal?

Unless someone can prove to me why we wouldn't need this for
fixing bugs I still thing this is only a good system to hint at
the developer that they shouldn't be using this class unless they
have to.

I could envision the compiler using the overide keyword to force
the developer to acknowledge they are using a non-reccomended class
like so:

// This class implements a polyfill which fixes a bug in the
keychain class
override class PolyfillForKeychainBug: Keychain {

}

Without this the compiler would throw an error "Non-open class can't
be overridden without `override` keyword"

> * Does this proposal fit well with the feel and direction of
> Swift?

It does in terms of safety but not in terms of simplicity.

> * If you have used other languages or libraries with a
> similar feature, how do you feel that this proposal
> compares to those?

It works like Java but I never liked final

> * How much effort did you put into your review? A glance, a
> quick reading, or an in-depth study?

I’ve read the proposal

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_________________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Sent from my iPhone

Even in Java, it is a bad idea to leave classes subclassable; but having to remember to add final is a chore.

I still think it is worth doing that chore. The fact of the matter is that Java did not and is not enforcing that default and how many widely used production languages you know that do enforce this by default instead of asking library authors to do this bit of work?

People keep talking about just adding final. This *is not* an alternative. We are not talking about preventing subclasses by default (i.e. final by default).

We are talking about preventing subclasses *in other modules* by default (i.e. sealed by default). The alternative would be to introduce a sealed keyword (or similar).

There are times when you *need* to use subclasses inside your module. Some or all of them may not even be directly visible externally (class clusters). However, you *do not* want any new subclasses added as you know that is not likely to end well. This is why having sealed, not just final, is important.

By choosing sealed as a default rather than final, we are keeping the "subclassable by default" status *within* modules. This facilitates experimentation and eliminates the need for application level code to opt-in to subclassing while still making external API contracts explicit and therefore hopefully more robust. It is the default most in-line with the values and goals of Swift.

'final' and 'sealed' are two very different things. Let's please keep this focused on what is actually being proposed.

···

Sent from my iPad

On Jul 9, 2016, at 3:48 AM, Goffredo Marocchi via swift-evolution <swift-evolution@swift.org> wrote:

On 8 Jul 2016, at 15:09, Károly Lőrentey via swift-evolution <swift-evolution@swift.org> wrote:

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Ok, then I should ask you to consider what I said earlier and substitute "final by default" with "sealed by default" and if we have this sealed keyword not to make it the default.
Trust coders and people a bit more instead of resorting to more involved obligatory processes instead. Do we really need in this case to seal modules by default? Can we just have a keyword that the library author can use and express this intent explicitly?

Usually, look at some countries like Italy in a legislative sense, when you attempt to regulate in a way that everything is kind of banned except what you explicitly allow you get more convoluted, complex, and nerve wrecking solutions than allowing everything except what you explicitly ban (reviewing it all over time of course).

···

Sent from my iPhone

On 9 Jul 2016, at 13:36, Matthew Johnson <matthew@anandabits.com> wrote:

Sent from my iPad

On Jul 9, 2016, at 3:48 AM, Goffredo Marocchi via swift-evolution <swift-evolution@swift.org> wrote:

Sent from my iPhone

On 8 Jul 2016, at 15:09, Károly Lőrentey via swift-evolution <swift-evolution@swift.org> wrote:

Even in Java, it is a bad idea to leave classes subclassable; but having to remember to add final is a chore.

I still think it is worth doing that chore. The fact of the matter is that Java did not and is not enforcing that default and how many widely used production languages you know that do enforce this by default instead of asking library authors to do this bit of work?

People keep talking about just adding final. This *is not* an alternative. We are not talking about preventing subclasses by default (i.e. final by default).

We are talking about preventing subclasses *in other modules* by default (i.e. sealed by default). The alternative would be to introduce a sealed keyword (or similar).

There are times when you *need* to use subclasses inside your module. Some or all of them may not even be directly visible externally (class clusters). However, you *do not* want any new subclasses added as you know that is not likely to end well. This is why having sealed, not just final, is important.

By choosing sealed as a default rather than final, we are keeping the "subclassable by default" status *within* modules. This facilitates experimentation and eliminates the need for application level code to opt-in to subclassing while still making external API contracts explicit and therefore hopefully more robust. It is the default most in-line with the values and goals of Swift.

'final' and 'sealed' are two very different things. Let's please keep this focused on what is actually being proposed.

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Hi,

However, you *do not* want any new subclasses added as you know that is not likely to end well.

Im curious, what kind of real-world scenario would "not end well" cover?

I’m genuinely curious, since Im still on the fence about this, but am willing to be convinced… if sealed by default brings more positives than negatives…

Thanks in advance.

Andre

···

2016/07/09 21:36、Matthew Johnson via swift-evolution <swift-evolution@swift.org> のメール:

Sent from my iPad

On Jul 9, 2016, at 3:48 AM, Goffredo Marocchi via swift-evolution <swift-evolution@swift.org> wrote:

Sent from my iPhone

On 8 Jul 2016, at 15:09, Károly Lőrentey via swift-evolution <swift-evolution@swift.org> wrote:

Even in Java, it is a bad idea to leave classes subclassable; but having to remember to add final is a chore.

I still think it is worth doing that chore. The fact of the matter is that Java did not and is not enforcing that default and how many widely used production languages you know that do enforce this by default instead of asking library authors to do this bit of work?

People keep talking about just adding final. This *is not* an alternative. We are not talking about preventing subclasses by default (i.e. final by default).

We are talking about preventing subclasses *in other modules* by default (i.e. sealed by default). The alternative would be to introduce a sealed keyword (or similar).

There are times when you *need* to use subclasses inside your module. Some or all of them may not even be directly visible externally (class clusters). However, you *do not* want any new subclasses added as you know that is not likely to end well. This is why having sealed, not just final, is important.

By choosing sealed as a default rather than final, we are keeping the "subclassable by default" status *within* modules. This facilitates experimentation and eliminates the need for application level code to opt-in to subclassing while still making external API contracts explicit and therefore hopefully more robust. It is the default most in-line with the values and goals of Swift.

'final' and 'sealed' are two very different things. Let's please keep this focused on what is actually being proposed.

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

I wanted to share a concrete use case that Daniel Dunbar relayed to me. He was working on a closed class hierarchy like the ones discussed here, where all of the subclasses are within a single module, but they are all public. The class also has a required initializer for dynamic construction, so that they could write something like this:
internal struct ModelContext { /*…*/ }
public class ModelBase {
  internal required init(context: ModelContext) { /*…*/ }
  // …
}
public class SimpleModel: ModelBase {
  internal required init(context: ModelContext) { /*…*/ }
}
public class MoreComplicatedModel: ModelBase { /*…*/ }
// (within some other type)
public func instantiateModelObject<Model: ModelBase>(_ type: Model) -> Model {
  return type.init(context: self.context)
}
That is, a public entry point calls a required initializer with an internal argument type. This is the only way to instantiate Model objects, and the internal context type doesn’t leak out into the public API.
Of course, Swift doesn’t allow this. If someone outside of the module subclasses ModelBase, there’s no way for them to provide the dynamically-dispatched 'init(context:)’, because they don’t have access to the internal ModelContext. The author of the library has to make the required initializers public, and either set the ModelContext separately or make it public as well. Even though no one outside the module should be using these APIs.

Can you remind us why does Swift need required initializers to have the same access as the containing class?

Because without required initializers you can't do something like:

let cl: MyClass.Type = ...
let instance = cl.init(value: 0)

where MyClass has a required init(value: Int).

The issue here is that in order to be able to do this, subclasses that implement another initializer must implement this one as well - so it must be visible outside of the module if it is public.

···

On Jul 10, 2016, at 1:37 AM, Károly Lőrentey via swift-evolution <swift-evolution@swift.org> wrote:
On 2016-07-09 04:39:01 +0000, Jordan Rose via swift-evolution said:

Having only package-internal constructors in the public base class is the usual pattern for exporting a sealed class hierarchy in some other languages, like Java.

--
Károly
@lorentey

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

I’m afraid it isn’t my codebase, so I can’t speak to this directly, but I don’t see anything wrong with this pattern. Dynamic initialization is useful, and internal, component-specific context is hardly uncommon; if the library used either of these alone we would likely have no problem with it. Two features not composing well does sometimes indicate a flaw in the design.

Jordan

···

On Jul 8, 2016, at 23:47, L. Mihalkovic <laurent.mihalkovic@gmail.com> wrote:

Regards
(From mobile)

On Jul 9, 2016, at 6:39 AM, Jordan Rose via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

[Proposal: https://github.com/apple/swift-evolution/blob/master/proposals/0117-non-public-subclassable-by-default.md ]

John has done a tremendous job supporting this proposal; the position he’s articulated very closely matches mine. Thank you to both John and Javier.

I wanted to share a concrete use case that Daniel Dunbar relayed to me. He was working on a closed class hierarchy like the ones discussed here, where all of the subclasses are within a single module, but they are all public. The class also has a required initializer for dynamic construction, so that they could write something like this:

internal struct ModelContext { /*…*/ }

public class ModelBase {
  internal required init(context: ModelContext) { /*…*/ }
  // …
}
public class SimpleModel: ModelBase {
  internal required init(context: ModelContext) { /*…*/ }
}
public class MoreComplicatedModel: ModelBase { /*…*/ }

// (within some other type)
public func instantiateModelObject<Model: ModelBase>(_ type: Model) -> Model {
  return type.init(context: self.context)
}

That is, a public entry point calls a required initializer with an internal argument type. This is the only way to instantiate Model objects, and the internal context type doesn’t leak out into the public API.

Then could it be that in the end it is the entire scaffolding that is poorly structured and in need of fixing, rather than altering the language to make the scaffolding work?

Sent from my iPhone

Of course, Swift doesn’t allow this. If someone outside of the module subclasses ModelBase, there’s no way for them to provide the dynamically-dispatched 'init(context:)’, because they don’t have access to the internal ModelContext.

Shouldn't Swift allow this? Wouldn't it be better if we found a different way to handle this than a brute force "you shall only subclass if I think you should"? Is that really an impossible cause that is worth us going completely the opposite direction of most programming languages?

There is no way to implement the required initializer from outside the module, because it uses an internal type, so what we’re looking for is that any subclasses from outside the module will never have the required initializer invoked on them. I suppose it would still be safe to allow a subclass from outside the module that did not provide any of its own initializers, but that seems like an even more complicated rule.

(It’s not sufficient to say that the dynamic initializers would just trap at run-time, because it’s possible that the base class has no public initializers.)

Can you tell me why the onus should not be on you, on library authors, to use final or an equivalent keyword to indicate no subclassing is allowed and thus make this intentional?

I am really not sold on why classes should not be subclassable by default. Not all classes suffer of the problem you mention and for those cases you should be able to express your intention explicitly. I am quite against this being a compiler default.

I admit that this use case says nothing about whether “sealed” should be the default or just available.

I think that security by ignorance, which is what automagically enforced rules tend to produce over time, does have some side effects.

This isn’t really a security issue; it’s a compiler-aided correctness issue. I’ll go more into that in my other email.

Jordan

···

On Jul 9, 2016, at 01:44, Goffredo Marocchi <panajev@gmail.com> wrote:

On 9 Jul 2016, at 05:39, Jordan Rose via swift-evolution <swift-evolution@swift.org> wrote:

Sure. A required initializer is one that will be invoked on an unknown subclass of the type, as in the instantiateModelObject method above. That means that it must be present on all subclasses, because it’s generally impossible to prove that a particular subclass will not be passed to instantiateModelObject.

Swift currently doesn’t allow this at all, but we could imagine making it a run-time error instead of a compile-time error, i.e. if instantiateModelObject is called on a class that doesn’t provide its own implementation of init(context:), the program would trap. That’d be giving up some compiler-provided safety for run-time flexibility, which can certainly be desirable.

However, this is just the tip of the iceberg. In the implementation of the subclass, there has to be a call to one of the superclass's initializers. If all of the superclass’s initializers are non-public, then there’s no way to write your own initializer. (This is actually true in Swift today.) This isn’t quite the same as sealed-by-default because the subclass could just inherit the superclass’s initializers, but it’s very close: the only way to instantiate such a class would be through a required initializer, which you couldn’t customize.

In that sense, Swift already has something very similar to sealed-by-default, which just doesn’t work very well in the particular use case where you also need to use dynamic initialization.

Jordan

···

On Jul 9, 2016, at 16:37, Károly Lőrentey via swift-evolution <swift-evolution@swift.org> wrote:

On 2016-07-09 04:39:01 +0000, Jordan Rose via swift-evolution said:

I wanted to share a concrete use case that Daniel Dunbar relayed to me. He was working on a closed class hierarchy like the ones discussed here, where all of the subclasses are within a single module, but they are all public. The class also has a required initializer for dynamic construction, so that they could write something like this:
internal struct ModelContext { /*…*/ }
public class ModelBase {
  internal required init(context: ModelContext) { /*…*/ }
  // …
}
public class SimpleModel: ModelBase {
  internal required init(context: ModelContext) { /*…*/ }
}
public class MoreComplicatedModel: ModelBase { /*…*/ }
// (within some other type)
public func instantiateModelObject<Model: ModelBase>(_ type: Model) -> Model {
  return type.init(context: self.context)
}
That is, a public entry point calls a required initializer with an internal argument type. This is the only way to instantiate Model objects, and the internal context type doesn’t leak out into the public API.
Of course, Swift doesn’t allow this. If someone outside of the module subclasses ModelBase, there’s no way for them to provide the dynamically-dispatched 'init(context:)’, because they don’t have access to the internal ModelContext. The author of the library has to make the required initializers public, and either set the ModelContext separately or make it public as well. Even though no one outside the module should be using these APIs.

Can you remind us why does Swift need required initializers to have the same access as the containing class?

Having only package-internal constructors in the public base class is the usual pattern for exporting a sealed class hierarchy in some other languages, like Java.

DaveA and I actually talked about this, and IIRC we decided it was completely safe semantically, and only left a few optimization opportunities on the table (things like knowing what a deinitializer might or might not do). However, we felt it was a more complicated model that still ended up with the “de facto sealed” case of all initializers being non-public, and so it wasn’t worth stopping at that point in the design space.

(It was a while ago, so I might have forgotten something else relevant.)

Jordan

···

On Jul 11, 2016, at 07:21, Jean-Daniel Dupas <mailing@xenonium.com> wrote:

Just a though, but why sealed classes have to be completely unsubclassable ?

Wouldn't it be possible to allow the user to subclass sealed class, but deny overriding of any public member.

I see a use case where a user want to extends an existing model by adding new properties and new methods to an object but can’t use composition because doing that will prevent to pass that object to the framework that expect the base object.

That would let user override existing class to extends them, but should not cause any side effect in the way the class should behave, and so would not affects preconditions and postconditions, and should not prevent optimization in whole module compilation, as the methods of the base class are considered final outside of the module.

Of course, it will introduce some fragility in the library, as adding new methods may conflict with user subclass methods, but no more than what would append if the user write extension to add new methods to the model.

P.S. There’s also an argument to be made for public-but-not-conformable protocols, i.e. protocols that can be used in generics and as values outside of a module, but cannot be conformed to. This is important for many of the same reasons as it is for classes, and we’ve gotten a few requests for it. (While you can get a similar effect using an enum, that’s a little less natural for code reuse via protocol extensions.)

Would public-but-not-conformable protocols by default be the next step, then, in Swift's evolution?

I personally think it’s a reasonable place to go next, which is why I brought it up. However, I don’t think it’s critical enough to get into Swift 3 when we’re already so busy, and when there are multiple non-source-breaking ways to get a similar effect later: adding a “sealed” annotation (so, giving up on “by default” for protocols) and allowing requirements to have more narrow access than the protocol (thus making it impossible to conform).

Jordan

Jean, given this proposal it will be possible if the developer of the
library intends so. You'll have to have unsealed classes to be able to
subclass them and unsealed methods so you can override. It is possible
to just allow subclassing without allowing overriding, just like
final.

As for conflicts I don't think so. If you declare a new method with
the same name as an existing one without overriding, it will become a
new method and the base class won't even know that new method exists.
C# allows this but uses the keyword new (instead of override) to
clarify a new method is being introduced instead of the existing one
but as far as I see there is no such need in Swift. I'm also not sure
we can override a method inside an extension but if so, this provides
a new point of extension inside a class that is not subclassable.

L

···

On 11 July 2016 at 11:21, Jean-Daniel Dupas via swift-evolution <swift-evolution@swift.org> wrote:

Just a though, but why sealed classes have to be completely unsubclassable ?

Wouldn't it be possible to allow the user to subclass sealed class, but deny
overriding of any public member.

I see a use case where a user want to extends an existing model by adding
new properties and new methods to an object but can’t use composition
because doing that will prevent to pass that object to the framework that
expect the base object.

That would let user override existing class to extends them, but should not
cause any side effect in the way the class should behave, and so would not
affects preconditions and postconditions, and should not prevent
optimization in whole module compilation, as the methods of the base class
are considered final outside of the module.

Of course, it will introduce some fragility in the library, as adding new
methods may conflict with user subclass methods, but no more than what would
append if the user write extension to add new methods to the model.

Le 11 juil. 2016 à 05:38, Jordan Rose via swift-evolution > <swift-evolution@swift.org> a écrit :

[Proposal:
https://github.com/apple/swift-evolution/blob/master/proposals/0117-non-public-subclassable-by-default.md
]

(This is my second response to this proposal. The previous message shared a
use case where public-but-non-subclassable made things work out much better
with required initializers. This one has a bit more ideology in it.)

As many people have said already, this proposal is quite beneficial to
library designers attempting to reason about their code, not just now but in
the future as well. The model laid out in the Library Evolution document
(often referred to as “resilience”) supports Swift libraries that want to
preserve a stable binary and source interface.

In the Swift 2 model (and what’s currently described in that document), a
public class must be final or non-final at the time it is published. It’s
clearly not safe to add ‘final' in a later version of the library, because a
client might already have a subclass; it’s also not safe to remove ‘final’
because existing clients may have been compiled assuming there are no
subclasses.

(Of course, we can remove this optimization, and make ‘final’ a semantic
contract only. I’m deliberately avoiding most discussion of performance, but
in this parenthetical I’ll note that Swift makes it possible to write code
that is slower than Objective-C. This is considered acceptable because the
compiler can often optimize it for a particular call site. For those who
want more information about the current implementation of some of Swift’s
features, I suggest watching the “Understanding Swift Performance” talk from
this year’s WWDC.)

With this proposal, a public class can be non-publicly-subclassable or
publicly-subclassable. Once a class is publicly-subclassable (“open”), you
can’t go back, of course. But a class that’s not initially open could become
open in a future release of the library. All existing clients would already
be equipped to deal with this, because there might be subclasses inside the
library. On the other hand, the class can also be marked ‘final’, if the
library author later realizes there will never be any subclasses and that
both client authors and the compiler should know this.

One point that’s not covered in this proposal is whether making a class
‘open’ applies retroactively, i.e. if MagicLib 1.2 is the first version that
makes the Magician class ‘open’, can clients deploy back to MagicLib 1.0 and
expect their subclasses to work? My inclination is to say no; if it’s
possible for a non-open method to be overridden in the future, a library
author has to write their library as if it will be overridden now, and
there’s no point in making it non-open in the first place. That would make
‘open’ a “versioned attribute” in the terminology of Library Evolution,
whatever the syntax ends up being.

---

Okay, so why is this important?

It all comes down to reasoning about your program’s behavior. When you use a
class, you’re relying on the documented behavior of that class. More
concretely, the methods on the class have preconditions
(“performSegue(withIdentifier:sender:) should not be called on a view
controller that didn’t come from a storyboard”) and postconditions (“after
calling loadViewIfNeeded(), the view controller’s view will be loaded”).
When you call a method, you’re responsible for satisfying its preconditions
so it can deliver on the postconditions.

I used UIViewController as an example, but it applies just as much to your
own methods. When you call a method in your own module—maybe written by you,
maybe by a coworker, maybe by an open source contributor—you’re expecting
some particular behavior and output given the inputs and the current state
of the program. That is, you just need to satisfy its preconditions so it
can deliver on the postconditions. If it’s a method in your module, though,
you might not have taken the trouble to formalize the preconditions and
postconditions, since you can just go look at the implementation. Even if
your expectations are violated, you’ll probably notice, because the conflict
of understanding is within your own module.

Public overriding changes all this. While an overridable method may have
particular preconditions and postconditions, it’s possible that the
overrider will get that wrong, which means the library author can no longer
reason about the behavior of their program. If they do a poor job
documenting the preconditions and postconditions, the client and the library
will almost certainly disagree about the expected behavior of a particular
method, and the program won’t work correctly.

"Doesn’t a library author have to figure out the preconditions and
postconditions for a method anyway when making it public?" Well, not to the
same extent. It’s perfectly acceptable for a library author to document
stronger preconditions and weaker postconditions than are strictly
necessary. (Maybe 'performSegue(withIdentifier:sender:)’ has a mode that can
work without storyboards, but UIKit isn’t promising that it will work.) When
a library author lets people override their method, though, they're
promising that the method will never be called with a weaker precondition
than documented, and that nothing within their library will expect a
stronger postcondition than documented.

(By the way, the way to look at overriding a method is the inverse of
calling a method: you need to deliver on the postconditions, and you can
assume the caller has satisfied the preconditions. If your understanding of
those preconditions and postconditions is wrong, your program won’t work
correctly, just like when you’re calling a method.)

This all goes double when a library author wants to release a new version of
their library with different behavior. In order to make sure existing
callers don’t break, they have to make sure all of the library’s documented
preconditions are no stronger and postconditions are no weaker for public
API. In order to make sure existing subclassers don’t break, they have to
make sure all of the library’s documented preconditions are no weaker and
postconditions are no stronger for overridable API.

(For a very concrete example of this, say you’re calling a method with the
type '(Int?) -> Int’, and you’re passing nil. The new version of the library
can’t decide to make the parameter non-optional or the return value
optional, because that would break your code. Similarly, if you’re
overriding a method with the type ‘(Int) -> Int?’, and returning nil, the
new version of the library can’t decide to make the parameter optional or
the return value non-optional, because that would break your code.)

So, "non-publicly-subclassable" is a way to ease the burden on a library
author. They should be thinking about preconditions and postconditions in
their program anyway, but not having to worry about all the things a client
might do for a method that shouldn’t be overridden means they can actually
reason about the behavior—and thus the correctness—of their own program,
both now and for future releases.

---

I agree with several people on this thread that
non-publicly-subclassable-by-default is the same idea as
internal-by-default: it means that you have to explicitly decide to support
a capability before clients can start relying on it, and you are very
unlikely to do so by accident. The default is “safe” in that a library
author can change their mind without breaking existing clients.

I agree with John that even today, the entry points that happen to be public
in the types that happen to be public classes are unlikely to be good entry
points for fixing bugs in someone else's library. Disallowing overriding
these particular entry points when a client already can't override internal
methods, methods on structs, methods that use internal types, or top-level
functions doesn’t really seem like a loss to me.

Library design is important. Controlling the public interface of a library
allows for better reasoning about the behavior of code, better security
(i.e. better protection of user data), and better maintainability. And
whether something can be overridden is part of that interface.

Thanks again to Javier and John for putting this proposal together.
Jordan

P.S. There’s also an argument to be made for public-but-not-conformable
protocols, i.e. protocols that can be used in generics and as values outside
of a module, but cannot be conformed to. This is important for many of the
same reasons as it is for classes, and we’ve gotten a few requests for it.
(While you can get a similar effect using an enum, that’s a little less
natural for code reuse via protocol extensions.)

P.P.S. For those who will argue against “better security”, you’re correct:
this doesn’t prevent an attack, and I don’t have much expertise in this
area. However, I have talked to developers distributing binary frameworks
(despite our warnings that it isn’t supported) who have asked us for various
features to keep it from being easy.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Terms of Service

Privacy Policy

Cookie Policy