Inside `fun <T> T?.ifNull(default: T): T`, how do you correctly handle `this` being null, and what role do smart casts play?
answer
- `this` is `T?` inside the body
- Elvis `?:` for one-line fallback
- `if (this != null)` → smart cast to `T`
- Unbounded `T` only gives Any? members
- Avoid `!!` — defeats the purpose
basics
~10 sInside, this may be null, so test it. After if (this != null) the compiler narrows the type to non-null, letting you use it safely. Or just use Elvis: return this ?: default.
solid answer
~40 sWith a nullable generic receiver `T?`, `this` has type `T?` inside the body. You handle null with the Elvis operator (`return this ?: default`), a safe call (`this?.member`), or an explicit `this == null` / `this != null` check. After such a check, Kotlin applies a **smart cast**: in the branch where `this != null`, `this` is treated as `T` (non-null) so you can access members without `?.`. Smart casts work for `val`/stable receivers; the receiver `this` is stable here. With generics, accessing members of `T` is limited unless `T` is bounded (e.g. `<T : Comparable<T>>`), but null handling itself is independent of the bound. Avoid `!!` — it defeats the point and can throw. Prefer Elvis for a single fallback expression; use explicit checks when you need a multi-statement non-null branch.
code
kotlin · 12 linesfun <T> T?.ifNull(default: T): T = this ?: default
fun <T : Any> T?.describe(): String {
if (this == null) return "<none>"
return "value=$this hash=${hashCode()}" // smart-cast to T
}
fun main() {
val x: Int? = null
println(x.ifNull(42)) // 42
println(x.describe()) // <none>
}go deeper
Can use Elvis ?: to return a default when this is null.
Explains smart casting after a null check and the Any? bound limit on unbounded T.
Chooses between Elvis, safe call, and explicit branches deliberately and bounds T when richer members are needed.
Reasons about how nullable generic receivers shape a reusable utility API and stability requirements for smart casts in concurrent/var contexts.
## The setup ```kotlin fun <T> T?.ifNull(default: T): T = this ?: default ``` Here `T` is a **generic type parameter** and the receiver type is `T?` — a nullable receiver whose underlying type is generic. Inside the body, `this` has type `T?`. ## Three ways to handle null in the body **1. Elvis operator `?:`** — concise, expression-form: ```kotlin fun <T> T?.ifNull(default: T): T = this ?: default ``` Reads: if `this` is non-null use it, else use `default`. **2. Safe call `?.`** — call a member only when non-null, yielding null otherwise: ```kotlin fun <T> T?.orToString(): String? = this?.toString() ``` **3. Explicit check + smart cast** — when you need multiple statements: ```kotlin fun <T : Any> T?.describe(): String { if (this == null) return "<none>" // smart cast: here `this` is T (non-null) return "value=$this hash=${hashCode()}" } ``` ## Smart casts A **smart cast** is the compiler automatically treating a value as a more specific (here, non-null) type after a check, without an explicit cast. After `if (this != null)`, inside that branch `this` is `T`, so member access needs no `?.`. Requirements: the checked entity must be **stable** (a `val`, or the receiver `this`); a mutable `var` captured by a closure may block the smart cast. ## Generics caveat `T` is unbounded, so its upper bound is `Any?`. You can call `toString()`, `hashCode()`, `equals()` (members of `Any?`), but nothing more specific unless you **bound** `T`: ```kotlin fun <T : Comparable<T>> T?.maxWith(other: T): T = if (this == null) other else maxOf(this, other) ``` ## Why not `!!`? `this!!` throws `NullPointerException` if null — that re-introduces the very crash a nullable receiver is meant to avoid. Use it only when null is genuinely impossible and you can document why. ## Summary - `this` is `T?` inside the body. - Elvis `?:` for a single fallback; `?.` for null-tolerant member access; explicit check for multi-statement branches. - Smart casts narrow `this` to `T` after `!= null`. - Bound `T` if you need members beyond `Any?`.
- Why can you only call `toString()`, `equals()`, `hashCode()` on an unbounded `T`?An unbounded type parameter `T` has the implicit upper bound `Any?`, exposing only `Any`'s members. To call more, add a bound like `<T : Comparable<T>>` or `<T : SomeInterface>`.
- When can a smart cast fail to apply to `this`?`this` (the receiver) is stable, so it usually works. Smart casts fail on mutable `var`s, custom getters, or values modifiable between check and use — but those don't apply to the receiver itself.
saying these in an interview costs you the question
- Using `this!!` instead of null-safe handling
- Assuming `this` is non-null without a check
- Thinking you can call arbitrary methods on unbounded `T`
- Confusing the receiver's nullability with the type parameter's bound
- Believing Elvis only works on String/collections