Conform Never to Codable
01 何が問題だったのか
Swiftの Never 型は「値を持ちえない型」として、「この関数は返らない」「このジェネリックパラメータは使われない」といった不在を型で表現するために使われます。一方、Swiftのコンパイラは Codable なメンバーだけで構成された型に対して Codable 適合を自動合成でき、ジェネリック型でも条件付き適合としてこれを利用することができます。
たとえば次の Either 型は、両方のパラメータが Codable なときだけ Codable になります。
enum Either<A, B> {
case left(A)
case right(B)
}
extension Either: Codable where A: Codable, B: Codable {}
ところが、これまで Never は Codable に適合していなかったため、Either<Int, Never> のように Never をパラメータに与えるとこの条件付き適合が成立せず、Codable として扱えませんでした。Never の値は作れない以上、エンコード・デコードの挙動で困ることは何もないはずなのに、Codable の型パラメータとして Never を渡せないせいで、ジェネリック型の利用側で不要な分岐や回避策を強いられていました。
02 どのように解決されるのか
標準ライブラリで Never に Encodable と Decodable(合わせて Codable)への適合を追加します。これにより、Never をジェネリックパラメータに含む型でも条件付き Codable 適合がそのまま成立するようになります。
// Either<Int, Never> もそのまま Codable として扱える
let value: Either<Int, Never> = .left(42)
let data = try JSONEncoder().encode(value)
let decoded = try JSONDecoder().decode(Either<Int, Never>.self, from: data)
Encodable 側
Never のインスタンスは原理的に存在しないため、encode(to:) は呼び出される可能性がありません。実装は空のメソッドで十分です。
Decodable 側
init(from:) は Never のインスタンスを返さなければならない初期化子ですが、Never の値は作れません。一方で、デコード対象の入力が不正である可能性はプログラマのミスとは限らないため、fatalError でプロセスを落とすのは不適切です。そこで、デコードを試みた場合は DecodingError.typeMismatch をスローする実装になっています。呼び出し側は通常の Codable と同じくエラーを try で受け取れます。
do {
_ = try JSONDecoder().decode(Never.self, from: Data("null".utf8))
} catch let DecodingError.typeMismatch(type, context) {
// type は Never.self、context に詳細が入る
}
既存の適合との関係
もし既存のコードで Never に対して Encodable / Decodable 適合を独自に宣言していた場合、標準ライブラリ側の適合と重複する形になるため、Conformance of 'Never' to protocol 'Encodable' was already stated in the type's module 'Swift' のような警告が出るようになります。Never の値は作れない以上、どちらの適合も挙動に差は生じないので、独自宣言を削除して標準ライブラリ側の適合に任せれば解消します。