Kotlin における Nothing の徹底解析

Kotlin の Type システムには、一見神秘的でありながら極めて重要な特殊な型である Nothing が存在します。
この型は Kotlin 開発において独自の役割を果たしています。
1. Nothing とは何か?
1.1 ソースコードから Nothing を見る
Nothing は class ですが、インスタンスを生成することはできません。
public class Nothing private constructor()したがって、Nothing 型の戻り値には、明示的な値を return することができません。
fun fetchNothing(): Nothing {
return ?
}では、なぜ Nothing が必要なのでしょうか?
なぜなら、Nothing の存在は、前回の記事で述べた「Everything is an Expression」という理念の最後のピースです。
1.2 Nothing は他の型のサブタイプである
下記のコードにおいて、関数 throwable の戻り値の型は何でしょうか?
fun throwable() {
throw RuntimeException()
}前回の Unit についての記事を読んだ方は、この throwable の戻り値は Unit だと思うかもしれませんが、 throwable に Unit を補完してみましょう。
fun throwable() {
throw RuntimeException()
return Unit
}しかし、この時点で IDE は return Unit が「Unreachable code」であると警告しています。
つまり、関数 throwable に戻り値がある場合、 throw RuntimeException() の時点で既に戻り処理が完了しており、コンパイラによって自動補完される return Unit に依存しているわけではないということです。
次に、記述方法を次のように変更してみましょう。
fun throwable() = throw RuntimeException()警告は「’Nothing’ return type needs to be specified explicitly」になります。
つまり、関数 throwable の実際の戻り値の型は Unit ではなく Nothing であるということです。
タイプを補完したら、次のようになります。
fun throwable(): Nothing {
throw RuntimeException()
}しかし、この時、注意深い人は気づくかもしれません。
Nothing は省略可能であれば、なぜ IDE は Redundant 'Unit' return type と同様に Redundant 'Nothing' return type と警告しないのか。
また、キーワード Nothing もグレー表示にならないのかと。
なぜなら、関数 throwable の戻り値は実際には Unit であるものの、この場合 Nothing は Unit のサブタイプとして返されるからです。
同様に、Nothing は Int や String などのサブタイプとして返されることが分かります。
fun throwable(): Unit = throw RuntimeException()
fun throwable(): Int = throw RuntimeException()
fun throwable(): String = throw RuntimeException()言い換えれば、 Nothing は他のすべてのクラスとは異なり、すべての他の型のサブタイプであるということです!
2. NOTHING の使用例
2.1 例外を投げる際に戻り値がないことを指示する場合
fun throwable(): Nothing = throw RuntimeException("return Nothing!")より一般的な使用方法
fun fetchValue(id: Int): String {
val result = findById(id)
if (result != null) {
return result
} else {
throw NullPointerException("ID cannot be null!")
}
}
fun fetchValue(id: Int): String = findById(id) ?: throw NotFoundException()Kotlin 標準ライブラリの TODO も Nothing を利用して実装されています。
@kotlin.internal.InlineOnly
public inline fun TODO(): Nothing = throw NotImplementedError()2.2 型安全な空のコレクションを作成する場合
val empty: List<Nothing> = listOf()
var items: List<Item> = emptyKotlin 標準ライブラリの EmptyList も Nothing を利用して実装されています。
internal object EmptyList : List<Nothing>, Serializable, RandomAccess2.3 型安全な無限ループとして使用する場合
fun repeat(): Nothing {
while (true) {
// loop
}
}2.4 再帰の末尾として使用する場合
sealed class LinkedList<out T>
object Empty : LinkedList<Nothing>()
data class Node<T>(val value: T, val next: LinkedList<T>) : LinkedList<T>()
val list = Node(1, Node(2, Empty))2.5 クロスプラットフォーム開発の場合
クロスプラットフォーム開発において、異なるプラットフォーム間で型システムの一貫性を保つために Nothing を利用する場合があります。
結論
Tony Hoare(null の創始者)が「I call it (null references) my billion-dollar mistake」と言ったように、Kotlin における「Everything is an Expression」という設計哲学は、 null references を回避するための重要な理念です。
Nothing の登場は、すべてのコードパスに確定した型が存在することを保証する最後のピースとなります。
システム開発のお悩みはclocoにご相談ください!
clocoはシステム開発の支援を行っています。
最高のチームであなたのプロジェクトをお手伝いさせてください!
少しでもclocoに興味を持っていただけたらぜひホームページを御覧ください。



