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

Jordan Rose jordan_rose at apple.com
Sun Jul 10 20:08:18 CDT 2016


> On Jul 9, 2016, at 16:37, Károly Lőrentey via swift-evolution <swift-evolution at 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.

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

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


More information about the swift-evolution mailing list