Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?

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.

Default implementation for mutating methods

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 classes:

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()

Default implementation for initializers

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment