Kotlin における Nothing の徹底解析

タピオカ

Kotlin の Type システムには、一見神秘的でありながら極めて重要な特殊な型である Nothing が存在します。

この型は Kotlin 開発において独自の役割を果たしています。

1. Nothing とは何か?

1.1 ソースコードから Nothing を見る

Nothingclass ですが、インスタンスを生成することはできません。

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 だと思うかもしれませんが、 throwableUnit を補完してみましょう。

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 であるものの、この場合 NothingUnit のサブタイプとして返されるからです。

同様に、NothingIntString などのサブタイプとして返されることが分かります。

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 標準ライブラリの TODONothing を利用して実装されています。

@kotlin.internal.InlineOnly
public inline fun TODO(): Nothing = throw NotImplementedError()

2.2 型安全な空のコレクションを作成する場合

val empty: List<Nothing> = listOf()
var items: List<Item> = empty

Kotlin 標準ライブラリの EmptyListNothing を利用して実装されています。

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に興味を持っていただけたらぜひホームページを御覧ください。

カテゴリー: スマホアプリ

タピオカ