[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.

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.

If ModelBase were public-but-not-subclassable, however, the code is perfectly 
fine. The initializer and the helper type don’t need to be public, and clients 
of the library see only what they need.

This is just one use case. I don’t want to say it’s a general model for 
everyone’s code. However, it does point to a desire for 
public-and-not-subclassable classes; any other solution would either require 
the library author making more things public, or the compiler making it 
possible to accidentally call an unimplemented initializer.

I’ll send a separate message with my thoughts as the primary author of the 
Library Evolution model, to keep those discussions distinct. That one will have 
a bit more ideology in it. :-)

Jordan

P.S. “Why not use protocols?” When a protocol has an initializer requirement, 
it still forces all subclasses of a conforming class to provide an 
implementation, i.e. the conforming class’s initializer still needs to be 
declared ‘required’. That means it’s subject to the same restriction: a 
required initializer must have as much access as the containing class.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to