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> = empty
Kotlin 標準ライブラリの EmptyList
も Nothing
を利用して実装されています。
internal object EmptyList : List<Nothing>, Serializable, RandomAccess
2.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に興味を持っていただけたらぜひホームページを御覧ください。