Почему не может быть требованием свойства get-only в протоколе удовлетворяется свойство, которое соответствует?

Почему возникает следующий код:

protocol ProtocolA {
    var someProperty: ProtocolB { get }
}

protocol ProtocolB {}
class ConformsToB: ProtocolB {}

class SomeClass: ProtocolA { // Type 'SomeClass' does not conform to protocol 'ProtocolA'
    var someProperty: ConformsToB

    init(someProperty: ConformsToB) {
        self.someProperty = someProperty
    }
}

Ответ в этом подобном вопросе имеет смысл. Однако в моем примере свойство get-only. Почему это не должно работать? Является ли это недостатком Свифта, или есть какая-то причина, это имеет смысл?

Ответ 1

Нет никакой реальной причины, почему это не должно быть возможным, требование свойства только для чтения может быть ковариантным, поскольку возвращение экземпляра ConformsToB из свойства, введенного как ProtocolB, совершенно законно.

Swift сейчас просто не поддерживает его. Для этого компилятору пришлось бы генерировать thunk между таблицей свидетельских показаний протокола и соответствующей реализацией, чтобы выполнить необходимое преобразование типов (ы). Например, экземпляр ConformsToB должен быть помещен в контейнер в экзистенциальном контейнере, чтобы его набрали как ProtocolB (и нет способа вызывающий может это сделать, поскольку он может ничего не знать о вызываемой реализации).

Но опять же нет причин, по которым компилятор не должен этого делать. Откроется несколько отчетов об ошибках, этот, который специфичен для требований к свойствам только для чтения, и этот общий, в котором Слава Пестов, член команды Swift, говорит:

[...] мы хотим, чтобы протокольные свидетели и методы переопределялись в каждом случае, когда разрешено преобразование функции

Таким образом, это определенно похоже на то, что команда Swift хочет реализовать в будущей версии языка.

В то же время, поскольку @BallpointBen говорит, одним из способов является использование associatedtype:

protocol ProtocolA {
    // allow the conforming type to satisfy this with a concrete type
    // that conforms to ProtocolB.
    associatedtype SomeProperty : ProtocolB
    var someProperty: SomeProperty { get }
}

protocol ProtocolB {}
class ConformsToB: ProtocolB {}

class SomeClass: ProtocolA {

    // implicitly satisfy the associatedtype with ConformsToB.
    var someProperty: ConformsToB

    init(someProperty: ConformsToB) {
        self.someProperty = someProperty
    }
}

Но это довольно неудовлетворительно, так как это означает, что ProtocolA больше не используется как тип (поскольку он имеет associatedtype требования). Это также изменяет то, что говорит протокол. Первоначально он сказал, что someProperty может вернуть что-либо, что соответствует ProtocolB - теперь он говорит, что реализация someProperty имеет дело только с одним конкретным конкретным типом, который соответствует ProtocolB.

Другим обходным решением является просто определение свойства фиктивного состояния, чтобы удовлетворить требованиям протокола:

protocol ProtocolA {
    var someProperty: ProtocolB { get }
}

protocol ProtocolB {}
class ConformsToB: ProtocolB {}

class SomeClass: ProtocolA {

    // dummy property to satisfy protocol conformance.
    var someProperty: ProtocolB {
        return actualSomeProperty
    }

    // the *actual* implementation of someProperty.
    var actualSomeProperty: ConformsToB

    init(someProperty: ConformsToB) {
        self.actualSomeProperty = someProperty
    }
}

Здесь мы по существу пишем thunk для компилятора - но это также не особенно приятно, поскольку он добавляет ненужное свойство API.