跪拜 Guibai
← All articles
Kotlin · Android

Nothing Is a Type: How Kotlin's Bottom Type Eliminates '!!' in Android Code

By RockByte ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

Declaring throw-only helpers as : Nothing trades '!!' for smart casts, eliminating a class of null-safety boilerplate and letting the compiler verify which code paths never return.

Summary

Kotlin's type system places Any at the top of all non-null types, Unit in the middle as a type whose only value indicates 'done, no result', and Nothing at the very bottom as a subtype of every type. Nothing expresses code that never finishes normally: functions that always throw, expressions like error() or TODO(), and the throw keyword itself.

On Android, this matters in practice. A helper that always throws can be declared with return type Nothing, and the compiler then knows the subsequent code is unreachable when that helper is called along a null-check path. That turns a null-check failure into a smart cast, so developers stop sprinkling '!!' throughout the code. UiState<Nothing> in sealed interfaces work because Nothing is a subtype of every type, letting an empty loading state be assigned to any generic state parameter.

Unit is a real type with a single instance, unlike Java's void keyword. That allows it to appear in generics and function types like () -> Unit, in Result<Unit>, and in MutableSharedFlow<Unit> to emit events without payloads. On the JVM, ordinary Unit functions compile to void, so there's no runtime overhead. The practical takeaway: knowing where these three types sit in the ladder lets you write code that communicates intent to both humans and the compiler.

Takeaways
— Any is the supertype of all non-null Kotlin types; it always exposes equals(), hashCode(), and toString(), and maps to Java's Object on the JVM but lacks wait() and notify().
— Unit is a real type with exactly one instance, named Unit, and it can be used in generics and function types where Java's void cannot; ordinary Unit functions compile to void on the JVM.
— Nothing is a subtype of every type, has no values, and signals that a function never returns normally, as in throw, error(), or TODO().
— Declaring a helper that always throws with `: Nothing` lets the compiler smart-cast a nullable variable to its non-null type after the call, so `order.total` compiles without `!!`.
— In a sealed interface `OrderState<out T>`, using `data object Loading : OrderState<Nothing>` works because covariance makes `OrderState<Nothing>` a subtype of `OrderState<Order>` for every `Order`.
— The literal `null` is typed as `Nothing?`, the single value of that type, which is why `null` is assignable to any nullable type.
Conclusions

Nothing's real payoff is compiler flow analysis: once a function's return type is Nothing, every call site becomes a source of unreachable-code information, which is why Kotlin can reason about nullability without runtime checks.

Unit being a type instead of a keyword is what enables Kotlin's function type syntax and event patterns; Java developers have to fake it with Void and null returns.

The UiState<Nothing> example shows the bottom type isn't just an academic curiosity; it directly enables a single Loading object to be reused across all generic state types.

Concepts & terms
Nothing
A Kotlin type with no possible values that is a subtype of every other type. Functions returning Nothing never return normally; they throw or loop forever. It lets the compiler treat following code as unreachable.
Unit
A type representing a completed operation with no result value. It has a single instance, also called Unit, and can be used in generics where Java's void cannot. On the JVM it compiles to void when no result is needed.
Smart cast
Kotlin's compiler feature that automatically narrows a value's type after a type check. For example, inside `if (value is String)` the compiler allows `value.length` without an explicit cast. Combined with Nothing, it also narrows nullable types after a function that always throws is called.
Covariance (out)
In Kotlin, declaring a type parameter with `out` means a `Container<Subtype>` is a subtype of `Container<Supertype>`. This is why `OrderState<Nothing>` is assignable to `OrderState<Order>` for any `Order`.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗