I just want to pitch the problems. I currently don't have any concrete solutions for them, but I belive we should definitly discuss this, before ABI stabilization.
Swift protocol extensions allow "self
assigning" default implementations for mutating methods:
protocol Resettable {
mutating func reset()
init()
}
extension Resettable {
mutating func reset() {
self = Self() // assigning to 'self'
}
}
And you can:
class Foo : Resettable {
var value: Int
required init() {
value = 0
}
}
In this example Foo
conforms to Resettable
, by the default implementation of mutating func reset()
.
However, this can cause some unexpected behavior for class
es:
var ref1 = Foo()
let ref2 = ref1
assert(ObjectIdentifier(ref1) == ObjectIdentifier(ref2))
ref1.value = 42
assert(ObjectIdentifier(ref1) == ObjectIdentifier(ref2))
ref1.reset()
assert(ObjectIdentifier(ref1) != ObjectIdentifier(ref2))
assert(ref1.value == 0)
assert(ref2.value == 42)
From the perspective of call site, I think, this behavior is counterintuitive because we use reference types with an expectation: the referencing address would never be changed unless we explicitly replace the object by re-assigning to the variable in call site, e.g.,
var ref: Foo = Foo()
ref = Foo()
Similar to methods, initializers have this issue:
protocol HasDefault {
static var _default: Self { get }
init()
}
extension HasDefault {
init() {
self = Self._default // Here it is.
}
}
final class Foo : HasDefault {
let value: Int
init(value: Int) {
self.value = value
}
static var _default = Foo(value: 0)
}
let obj = Foo()
assert(obj.value == 0)
This allows us to implement a kind of "factory initializer".
protocol Factory {
init(factory: () -> Self)
}
extension Factory {
init(factory: () -> Self) {
self = factory()
}
}
class Animal {
var emoji: Character { return "❓" }
}
class Cat : Animal {
override var emoji: Character { return "🐱" }
}
class Dog : Animal {
override var emoji: Character { return "🐶" }
}
extension Animal : Factory {
convenience init(type: String) {
self.init(factory: {
switch type {
case "dog": return Dog()
case "cat": return Cat()
default: return Animal()
}
})
}
}
let dog = Animal(type: "dog")
assert(dog.emoji == "🐶")
assert(type(of: dog) == Dog.self)
However, I believe, this is NOT a proper way of implementing "factory initializers". We should introduce proper "factory initializer" syntax.