In https://github.com/apple/swift/pull/19151, I'm changing code generation for class convenience initializers so that they only have "allocating" entry points, which are responsible for allocating and initializing the entire object. (See Changing class convenience initializers to perform whole object allocation and @objc interop for more background.) With this change, convenience initializers are nearly identical to value or protocol extension initializers at the implementation level, since the self.init delegation within a convenience initializer is effectively initializing the self reference with a reference to the instance created by the self.init call rather than initializing the instance itself. At this point, it's not that big of a leap to allow the self reference to also be initialized by assignment, as it can be in value and protocol extension initializers, like this:
This opens the door to convenience initializers being able to use static factory methods or other Self-returning operations to produce their returned result, not only the result of other initializers. Swift's own standard library and Foundation overlay hack around this missing functionality by making classes conform to dummy protocols and using protocol extension initializers where necessary to implement this functionality:
protocol P {
static func instance() -> Self
}
extension P {
init() {
self = Self.instance()
}
}
class C: P {
static func instance() -> Self { ... }
convenience init(x: ()) {
self.init()
}
}
Because a class convenience initializer can already delegate to a protocol extension initializer, and a protocol extension initializer can produce an arbitrary class instance of Self type when invoked on a conforming class (including potentially a subclass of the covariant Self it was invoked on), I don't think there's any weakening of the invariants one can expect from an arbitrary convenience initializer—you already can't assume without further analysis that an initializer creates a fresh object or that the returned object has exactly the dynamic class type the initializer was invoked on. This seems like a small improvement that could eliminate some hacky workarounds in our and other people's codebases. What do you all think?
Is this a typo in the example, pref is never used?! Otherwise I don't understand the purpose of this example except that it shows an unambiguous init(pref:) as a factory init.
Other than that, Swift still does not allow the usage of Self in classes (SE-0068 is still not implemented after years). In the given example I read Self as it would mean the same thing like if you had a protocol with Self that this class conforms to. I had a short discussion on twitter [1] with @Slava_Pestov about the proposal a while ago. I must admit that I did not fully understand the technical issue with Self, but he said that we have to reconsider what Self would mean on classes. It seems that the reconsideration of Self touches this pitch since we need to clarify what the following code means:
extension Y {
convenience init(other: Self) {
self = other
}
}
Or what does Self means in the related thread from the [1] discussion in case of a generic type parameter:
Nevertheless I really like the general sense of this pitch especially if combined with SE-0068. Then I can finally refactor this pattern to something more naturally hidden:
// Before
protocol XibDesignable : AnyObject {}
extension XibDesignable where Self : UIView {
static func instantiateFromXib() -> Self {
let metatype = Self.self
let bundle = Bundle(for: metatype)
let nib = UINib(nibName: "\(metatype)", bundle: bundle)
guard let view = nib.instantiate(withOwner: nil, options: nil).first as? Self else {
fatalError("Could not load view from nib file.")
}
return view
}
}
// After:
extension UIView {
static func instantiateFromXib() -> Self {
let metatype = Self.self
let bundle = Bundle(for: metatype)
let nib = UINib(nibName: "\(metatype)", bundle: bundle)
guard let view = nib.instantiate(withOwner: nil, options: nil).first as? Self else {
fatalError("Could not load view from nib file.")
}
return view
}
}
final class View : UIView {
convenience init() {
self = Self.instantiateFromXib()
}
}
I think the best interpretation of SE-0068 is that Self in a class is the dynamic type of self, so your above example will be invalid. However, it's also not a useful thing to define. You'll never call Y(other: foo) directly, instead just referring to foo; and you'll never delegate to Y.init(other:) via self.init, since you can instead write self = other.
The invalid part is that Y is not while final other can be a subclass Z of Y where self = other would mean 'assign an instance of Z to a storage of Y' which cannot/shouldn't happen. Is that what you mean, more or less? So a better solution would be to come up with another way to refer the current type:
class A {
init() { ... }
init(other: InstanceSelf) { ... }
}
class B : A { ... }
let b = B()
let bb = B(other: b) // Valid since `InstanceSelf == B`
In that sense though, why do we need a restriction for factory init's to be convenience only?
Rule 1
If your subclass doesn’t define any designated initializers, it automatically inherits all of its superclass designated initializers. Rule 2
If your subclass provides an implementation of all of its superclass designated initializers—either by inheriting them as per rule 1, or by providing a custom implementation as part of its definition—then it automatically inherits all of the superclass convenience initializers.
Simply allowing to assign to self have a problem:
class Animal {
init() {}
convenience init(with instance: Animal) {
self = instance
}
}
class Cat : Animal {
override init() {}
}
class Dog : Animal {
override init() {}
}
let cat: Cat = Cat(with: Dog()) // Dog with Cat type???
Although we already have this problem with the current "hackish" factory initializer with protocol, if we allow assigning to self, I think we should amend Rule 2 to exclude "assigning to self" convenience initializers. i.e. those initializers should never be inherited.
Hackish factory initializer example which compiles in 4.2
protocol P {}
extension P {
init(_with instance: Self) {
self = instance
}
}
class Animal : P {
init() {}
convenience init(with instance: Animal) {
self.init(_with: instance)
}
}
class Cat : Animal {
override init() {}
}
class Dog : Animal {
override init() {}
}
let cat: Cat = Cat(with: Dog()) // Dog with Cat type???
self has Self type, not Animal type, so the type system still prevents you from writing something unsound by the normal convenience initializer inheritance rules, and we shouldn't change the rules because of an implementation detail of the body. Protocol initializers must produce Self as well. As @DevAndArtist noted, if we allowed references to Self as a type inside method bodies, you could say self = instance as! Self to explicitly force an initializer that only works dynamically when Self == Animal if that's what you want. It looks like a bug that this is accepted:
class Animal : P {
init() {}
convenience init(with instance: Animal) {
self.init(_with: instance)
}
}
As pointed out by @ahti in Workarounds for Self in classes, for static methods self is equivalent to self.Type. So your example above can be simpler:
// After:
extension UIView {
static func instantiateFromXib() -> Self {
let bundle = Bundle(for: self)
let nib = UINib(nibName: "\(self)", bundle: bundle)
guard let view = nib.instantiate(withOwner: nil, options: nil).first as? Self else {
fatalError("Could not load view from nib file.")
}
return view
}
}
However, what is missing is coercing to Self. Neither as? self nor as? Self is ok in classes, but latter works fine in protocols. On the other hand, is there a way to workaround this if one could assign to self? I guess there would still be type mismatch?
I actually fixed this already, but staged it in with -swift-version 5 since it's source breaking (Foundation depends on the unsafe behavior). If you build with -swift-version 5 you will see the type checker rejects your protocol extension example:
$ ../build/Ninja-RelWithDebInfoAssert/swift-macosx-x86_64/bin/swiftc a.swift -swift-version 5
a.swift:10:22: error: cannot convert value of type 'Animal' to expected argument type 'Self'
self.init(_with: instance)
^~~~~~~~
as! Self