Conform Never to Identifiable
01 何が問題だったのか
Never は、到達し得ないコードを表すための特殊な型で、値を持たない(uninhabited な)型です。SE-0215 により「blessed bottom type」と位置づけられ、Equatable / Hashable / Comparable / Error への適合が追加されました。ただし、このとき「すべてのプロトコルに暗黙的に適合するのではなく、価値がある都度、明示的に適合を追加していく」という方針が示されていました。
その後、SwiftUI をはじめとするフレームワークでは、Identifiable を関連型に要求する「再帰的な型パターン」のプロトコルが一般的になりました。こうしたプロトコルでは、再帰の終端として使う primitive な bottom type も、当然 Identifiable であることが求められます。
たとえば SwiftUI の TableRowContent プロトコルは、TableRowValue という Identifiable に適合した関連型を持ち、Never をその終端として使いたいケースがあります。
extension Never: TableRowContent {
public typealias TableRowBody /* TableRowContent に適合 */ = Never
public typealias TableRowValue /* Identifiable に適合する必要がある */ = Never
}
ところが Never は Identifiable に適合していないため、このような extension が書けず、Never を bottom type として活用できない状況がありました。Result の Failure == Never のような「絶対に起こらない」ケースを型で表すパターンを、Identifiable を要求するプロトコル階層に対しても素直に適用できなかった、ということです。
02 どのように解決されるのか
標準ライブラリ側で Never に Identifiable への適合を追加します。
@available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)
extension Never: Identifiable {
public var id: Never {
switch self {}
}
}
id プロパティは Never を返しますが、Never には値が存在しないため、self に対する switch は網羅すべきケースが一つも無く、本体が空でも型チェックを通ります。そもそも Never のインスタンスは構築できないので、この id が呼び出されることはあり得ません。SE-0215 で Equatable などを追加したときと同じ論法です。
この適合により、Never を Identifiable を要求する関連型の終端として使えるようになり、SwiftUI の TableRowContent のような再帰的プロトコルで bottom type を綺麗に表現できます。
extension Never: TableRowContent {
public typealias TableRowBody = Never
public typealias TableRowValue = Never
}
なお、この適合は OS バージョンで @available 制約がかかっている点に注意が必要です(macOS 12.0 / iOS 15.0 / watchOS 8.0 / tvOS 15.0 以降)。
既存コードへの影響
別モジュールですでに Never: Identifiable を宣言していた場合、コンパイラは「標準ライブラリ側ですでに適合が宣言されている」旨の警告を出し、標準ライブラリ側の id を採用します。Never の値は構築できないため、どちらの id 実装が使われるかは実行時の挙動には影響しません。自前の適合を削除すれば警告は解消できます。