Explain how a Nothing-returning function or `throw` enables expressions like `val x = a ?: error("...")` and why the compiler then treats following code as unreachable.
answer
- Elvis type = common supertype of branches
- Nothing branch collapses to the other type
- throw / error / return are Nothing expressions
- Unconditional Nothing call → unreachable warning
- Conditional exit → smart-cast on surviving path
basics
~20 sBecause a throw or a Nothing function can stand in for any type, you can put it after Elvis (?:) to get a non-null value or bail out. The compiler knows that path can't continue, so code after it is dead.
solid answer
~40 sThe Elvis operator `a ?: b` has the type that is the common supertype of `a`'s non-null type and `b`'s type. When `b` is `throw ...` or a `Nothing`-returning call like `error(...)`/`fail(...)`, its type is `Nothing`, the bottom type, which is a subtype of everything — so the result type is simply `a`'s non-null type. That is why `val x: String = a ?: error("...")` smart-casts `x` to non-null `String`. The compiler's control-flow analysis treats a `Nothing` result as a point of no return: statements after an unconditional Nothing call are flagged **unreachable**, and after a conditional one (e.g., inside an `if`) the negative branch's facts (like non-nullness) propagate as smart casts. This is the same machinery used by `requireNotNull`, `checkNotNull`, and `return`/`break`/`continue` in expression position.
code
kotlin · 5 linesfun firstUpper(words: List<String>?): Char {
val list = words ?: return ' ' // return : Nothing
val head = list.firstOrNull() ?: error("empty") // error : Nothing
return head.first().uppercaseChar()
}go deeper
Can use a ?: return/a ?: throw but may not explain the typing.
Explains the Elvis result type and that throw/error/return are Nothing expressions.
Connects Nothing to control-flow analysis: unreachable code plus smart-cast on the surviving path.
Reasons about least-common-supertype rules and how bottom-type substitution keeps inference sound across these idioms.
## The type of an Elvis expression `a ?: b` evaluates to `a` when `a` is non-null, otherwise to `b`. Its static type is the **least common supertype** of `a`'s non-null type and `b`'s type. - If `b` has type `Nothing` (the bottom type, subtype of everything), the common supertype collapses to `a`'s non-null type. ```kotlin fun parse(s: String?): Int { val text: String = s ?: throw IllegalArgumentException("null input") // ^ type Nothing, so `text` is non-null String return text.length } ``` `throw` is an **expression** of type `Nothing`, and `error(msg): Nothing`, `TODO(): Nothing`, or your own `fun fail(): Nothing` work the same way. ## Why following code becomes unreachable Kotlin runs a **control-flow analysis** pass. A call returning `Nothing` is a guaranteed exit, so: ```kotlin fun demo(x: Int) { fail("stop") // : Nothing println("never runs") // warning: unreachable code } ``` The compiler emits an *unreachable code* warning for anything after an unconditional Nothing call. ## Smart casts via the negative branch When the Nothing exit is conditional, the compiler learns facts on the surviving path: ```kotlin fun useName(user: User) { val name = user.name ?: return // null path exits the function // here `name` is smart-cast to non-null println(name.uppercase()) } ``` Here `return` is also of type `Nothing`. The same applies to `break`, `continue`, and `requireNotNull(x)` (which returns the non-null value and throws otherwise). ## Where the standard library leans on this - `requireNotNull(x)` / `checkNotNull(x)` → return non-null, throw on null. - `require(cond) { msg }` / `check(cond) { msg }` → fail path is Nothing-typed. - `error(msg)` and `TODO()` → both `: Nothing`. ## Key terms - **Least common supertype**: the most specific type assignable from both branches. - **Control-flow analysis**: the compiler pass that tracks reachability and nullness. - **Smart cast**: automatic narrowing of a value's type after a proven check.
- What is the static type of `a ?: throw E()` when `a: String?`?String (non-null), because the throw branch is Nothing, the subtype of everything, so the result is a's non-null type.
- Does `requireNotNull(x)` rely on Nothing?Its throwing path is Nothing-typed; the function returns the non-null value on success, enabling smart casts and `val y = requireNotNull(x)`.
saying these in an interview costs you the question
- Claiming the Elvis result is nullable even with a Nothing right side
- Not knowing throw/return are expressions of type Nothing
- Thinking unreachable-code warnings come from a linter, not the compiler
- Failing to connect Nothing to smart casts
- Believing `?: error(...)` could still yield null