Skip to content

Latest commit

 

History

History
90 lines (63 loc) · 6.74 KB

5.5.2 OpaqueType.md

File metadata and controls

90 lines (63 loc) · 6.74 KB

Opaque Type: some

  1. Opaque Types

Протоколы не позволяют использовать примитивные бинарные операции (н/р, сравнение), важной фичей в своё время было добавление типа Self, позволяющего обращаться к объекту, реализующему данный протокол. Однако, такая гибкость порождает неочевидную проблему.

any vs some

let vehicles: [some Vehicle] = [
    Car(),
    Car(),
    Car(),
]

Пример проблемы и решения

Так как Swift, используя экзистенциальные типы, не имеет представления о конкретном возвращаемом типе функции (а только о свойствах этого типа), компилятор выдает ошибку, известную как Protocol AnyProtocol can only be used as a generic constraint because it has Self or associated type requirements.

Решением проблемы выступило внедрение новой фичи — Opaque Types, позволяющей указать компилятору конкретный возвращаемый тип для каждой функции в коде. Аналогичные решения используют и в других языках. Реализация для Swift была перенесена из Rust, в котором разработчики языка придумали классный и лаконичный подход.

some

some используется вместе с протоколами чтобы создать an opaque type that represents something that is conformed to a specific protocol.

Проблема

Например, следующий код выдаст ошибку при компиляции:

protocol AnyProtocol: Equatable { }
struct MainClass: AnyProtocol { }
// код вызывает ошибку
// из-за экзистенциального контейнер 
func generate() -> AnyProtocol {
  return MainClass()
}
let main = generate()

В своей реализации протокол Equatable определяет свойство Self, позволяющее обращаться в объекту, реализующему этот протокол. Свойство используется для определения оператора ==.

При запуске этого примера кода, мы получим ошибку вида Protocol AnyProtocol can only be used as a generic constraint because it has Self or associated type requirements, которая говорит нам о том, что протоколы имеют некоторые ограничения и могут использоваться только в качестве Type Constraints (задавать правила для неабстрактных типов данных).

Дело в том, что компилятор Swift не знает какой конкретный тип может вернуться от этой функции в рантайме. Он знает только то, что тип реализует протокол AnyProtocol. В данном случае, функция могла бы вернуть значения Int: Equatable или Array: Equatable. Хотя их абстрактный тип данных схожий, конкретные типы невозможно сравнить оператором ==, о чем нам и сообщает компилятор.

Решение проблемы

Хотя под капотом все устроено немного сложнее. Проблему решили достаточно изящно — с помощью добавления синтаксической конструкции к возвращаемому значению функции:

protocol AnyProtocol: Equatable { }
struct MainClass: AnyProtocol { }
// код не вызывает ошибку
func generate() -> some AnyProtocol {
  return MainClass()
}
let main = generate()

Но каким образом это решило проблему? Для пользователя такая конструкция выглядит, как и раньше, — “возвращением абстрактного типа, реализующего протокол AnyProtocol”.

На самом деле, в случае использования Opaque Result Types компилятор Swift под капотом заранее определяет все конкретные возвращаемые типы для каждой функции программы. То есть после компиляции функция generate() возвращает тип MainClass, а не AnyProtocol. Это позволяет устранить ошибку, приведенную выше, потому что компилятор теперь знает о конкретном возвращаемом типе данных функции. Неужели это действительно необходимо?

Проблему и правда можно решить с помощью обычных Generics:

// код не вызывает ошибку
func generate<T: AnyProtocol>() -> T {
  return MainClass() as! T
}
// еще пример без force unwrapped
func collect<T: AnyProtocol>(value: T, and anotherValue: T) -> [T] {
  return [value, anotherValue]
}
// применение функции
// auto infer to collect<MainClass.Type>
// returns [MainClass(), MainClass()]
collect(value: MainClass(), and: MainClass())

И это вполне рабочий вариант. Тогда зачем нужны Opaque Types, если можно использовать шаблоны?

Во время использования generic информация о типе данных поступает от пользователя (то есть мы руками задаём нужный тип данных в параметрах функции). А Opaque Result Types позволяют делегировать выбор возвращаемого типа самой функции (а точнее — компилятору).


5.5.1 Generics Theme | Back To iOSWiki Contents | 5.5.3 Existential Types Theme