Swift Digest
SE-0319 | Swift Evolution

Conform Never to Identifiable

Proposal
SE-0319
Authors
Kyle Macomber
Review Manager
Tom Doron
Status
Implemented (Swift 5.5)

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
}

ところが NeverIdentifiable に適合していないため、このような extension が書けず、Never を bottom type として活用できない状況がありました。ResultFailure == Never のような「絶対に起こらない」ケースを型で表すパターンを、Identifiable を要求するプロトコル階層に対しても素直に適用できなかった、ということです。

02 どのように解決されるのか

標準ライブラリ側で NeverIdentifiable への適合を追加します。

@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 などを追加したときと同じ論法です。

この適合により、NeverIdentifiable を要求する関連型の終端として使えるようになり、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 実装が使われるかは実行時の挙動には影響しません。自前の適合を削除すれば警告は解消できます。