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

_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to