Huge +1 to this change.
I maintain code that uses hacks like this as well and I would love to get rid of it.
Huge +1 to this change.
I maintain code that uses hacks like this as well and I would love to get rid of it.
from me! I actually ran into this limitation yesterday.
+1. Have used the hack also!
+1 from my side, waiting for this since a long time.
+1, long overdue!!
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?
As per the automatic initializer inheritance rule:
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.
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)
}
}
The protocol extension method with contravariant Self
argument ought to be invoked with the protocol's Self
bound to the dynamic Self
of the class, not to Animal
, in which case self.init(_with: instance)
should not type check because the instance
argument is not of Self
type. This ought to be an error. I filed [SR-8713] Protocol extension methods invoked from non-final classes are type-checked incorrectly · Issue #51225 · apple/swift · GitHub.
Yes, that was just a typo, sorry. The only purpose of the argument there is to disambiguate the initializers.
So it should be type(of: pref).singletonInstance()
I guess?
As you noted, self
must still be assigned something of Self
type, so type(of: self)
is what I meant.
Ah okay, got it, thank you for clarification.
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
+1 on this concept.
++1 looks great!
+1
Is there a reason this is only for convenience initializers?
In a non-final class, a designated initializer can be called as part of a super.init delegation from a derived class. The "self" value is going to be a partially initialized instance of the derived class in this case, with the derived class's stored properties already initialized but nothing else. It wouldn't make sense to replace "self" with a fully initialized instance while it is in this state.