Avatar ·

Kotlin: Void, Unit и Nothing — в чем разница и когда что использовать?

Всем привет! Я недавно начал изучать Kotlin и перешел с Java. В Java я привык к void для методов, которые ничего не возвращают. Но в Kotlin я вижу сразу три похожих понятия: Void, Unit и Nothing. И я запутался.

Например, я пишу функцию, которая просто печатает приветствие:

fun greet(name: String) {
    println("Hello, $name!")
}

Компилятор говорит, что тип возврата — Unit. Но я могу написать и так:

fun greet(name: String): Unit {
    println("Hello, $name!")
}

А еще я видел в чужом коде Void (с большой буквы) и Nothing. Когда я попробовал использовать Nothing как возвращаемый тип, компилятор сказал, что функция никогда не завершится нормально.

Подскажите, пожалуйста:

  1. Чем отличаются Unit, Void и Nothing?
  2. Когда нужно использовать каждый из них?
  3. Почему в Kotlin не оставили просто void из Java?

Особенно меня смущает Nothing — зачем он вообще нужен, если есть Unit? Буду благодарен за понятные примеры!

<<>>

Краткий ответ: три разных концепции для трех разных ситуаций

В Kotlin Unit, Void и Nothing — это не взаимозаменяемые вещи. Они решают совершенно разные задачи. Давайте разберем их по порядку.

1. Unit — "я возвращаюсь, но ничего полезного не несу"

Unit — это аналог void в Java, но с одним важным отличием: Unit — это полноценный тип с единственным экземпляром (синглтоном). Это значит, что функция, возвращающая Unit, всегда завершается и возвращает объект Unit.

fun logMessage(msg: String): Unit {
    println("LOG: $msg")
    // return Unit — можно не писать, Kotlin делает это автоматически
}

// Можно использовать в generics:
fun <T> executeAndLog(action: () -> T): T {
    val result = action()
    println("Action executed, result type: ${result?.let { it::class.simpleName } ?: "Unit"}")
    return result
}

// Работает с функциональными интерфейсами:
val runnable: () -> Unit = { println("Running") }

Почему Unit лучше, чем void? Потому что Unit — это объект. Вы можете:

  • Использовать его в generic-типах (например, List<Unit>)
  • Возвращать из lambda-выражений
  • Передавать как аргумент

2. Void — "я из Java, используйте меня только для совместимости"

Void (с большой буквы) — это класс из Java (java.lang.Void). В Kotlin он существует только для совместимости с Java-кодом. Void — это нельзя создать экземпляр (конструктор приватный). Функция, возвращающая Void, должна явно возвращать null:

fun javaStyleMethod(): Void? {
    println("This is for Java interop")
    return null // обязательно, иначе ошибка компиляции
}

В чистом Kotlin Void использовать не нужно. Если вы видите Void в Kotlin-коде — это почти наверняка ошибка дизайна. Замените его на Unit.

3. Nothing — "я никогда не вернусь"

Nothing — это самый интересный тип. Он означает, что функция никогда не завершится нормально. Это может быть:

  • Бесконечный цикл
  • Всегда выбрасываемое исключение
  • Завершение программы
fun fail(message: String): Nothing {
    throw IllegalArgumentException(message)
    // return не нужен — функция никогда не дойдет до конца
}

fun infiniteLoop(): Nothing {
    while (true) {
        // бесконечный цикл
    }
}

// Классический пример — TODO():
fun notImplemented(): Nothing = TODO("Implement this later")

Nothing — это подтип всех типов (bottom type в терминах теории типов). Это значит, что вы можете использовать его везде, где ожидается любой другой тип:

val x: Int = fail("This will never be assigned") // OK, Nothing подтип Int
val y: String? = fail("Also works") // OK

// Пример с elvis-оператором:
val name: String = getUserName() ?: fail("User not found")
// Если getUserName() вернет null, fail() выбросит исключение
// Компилятор знает, что после fail() выполнение не продолжится

Сводная таблица

Тип Что означает Когда использовать Пример
Unit Функция завершается, но не возвращает ценных данных Всегда в чистом Kotlin вместо void fun print(): Unit
Void То же, что Unit, но из Java Только для Java-совместимости fun callback(): Void?
Nothing Функция никогда не завершается нормально Для исключений, бесконечных циклов, TODO() fun error(): Nothing

Почему в Kotlin нет void?

Потому что void в Java — это не тип, а ключевое слово. Это создавало проблемы в generics (нельзя написать List<void>). Kotlin сделал Unit полноценным типом, что дает больше гибкости и согласованности в системе типов.

<<>>

Практические рекомендации по выбору

Дополню отличный ответ коллеги несколькими практическими советами, которые помогут не ошибиться в реальном коде.

Когда писать Unit явно?

Обычно вы не пишете Unit явно — Kotlin подставляет его автоматически. Но есть случаи, когда явное указание помогает:

  • В generic-функциях: fun <T> process(): T where T : Any — если нужен Unit, пишите явно
  • В интерфейсах для ясности: interface ClickListener { fun onClick(): Unit }
  • В функциональных типах: val action: () -> Unit = {}

Типичные ошибки новичков

  1. Попытка вернуть Nothing из обычной функции — компилятор выдаст ошибку, если функция может завершиться нормально
  2. Использование Void в Kotlin-коде — это делает код менее идиоматичным и требует возврата null
  3. Путаница между Nothing и Unit — запомните: Unit = "я закончил", Nothing = "я не закончу"

Когда Nothing действительно полезен?

Кроме очевидных случаев (исключения, бесконечные циклы), Nothing отлично работает в цепочках вызовов:

fun validateInput(input: String?): String {
    return input ?: throw IllegalArgumentException("Input cannot be null")
    // Компилятор знает: после throw выполнение не продолжится
}

// Или с пользовательскими исключениями:
data class Config(val timeout: Int = 30)

fun loadConfig(): Config {
    return serverConfig ?: crash("Server config missing")
}

private fun crash(message: String): Nothing {
    error