liveBook · Manning

Smart cast

As explained in Kotlin in Action, 2E

A smart cast is Kotlin's compiler-supported automatic treatment of a value as a checked subtype within a valid control-flow branch.

A smart cast lets Kotlin combine a runtime type check with a usable subtype view. After a value passes an `is` check, code in the valid branch can call members of that subtype without an extra cast, redeclaration, or repeated check. This removes a common source of class-cast errors while keeping the check visible at the point of use. section 1.4.3 section 2.0

fun modify(value: Any) {
    if (value is String) {            #1
        println(value.uppercase())    #2
    }
}

Inside the true branch, `value is String` establishes that `value` is a `String`, so `uppercase()` is available directly. section 1.4.3

Smart casts are especially useful when a common type can represent several concrete shapes. Instead of checking a value and then manually converting it before accessing subtype-specific state, test its type and use it directly within that branch. The compiler recognizes the checked type there, preserving the safety that a separate cast would otherwise need to establish. section 1.4.3 section 2.3.6

Evaluating an expression tree

An `Expr` value can be either `Num`, holding an integer, or `Sum`, holding two `Expr` operands. Evaluation first distinguishes those cases: a `Num` branch returns its `value`; a `Sum` branch recursively evaluates `left` and `right`. This is a practical fit for smart casts because each type check unlocks the members needed for that case. The tree `Sum(Sum(Num(1), Num(2)), Num(4))` evaluates to `7`. section 2.3.6

Listing 2.18 Evaluating expressions with an if cascade
fun eval(e: Expr): Int {
    if (e is Num) {
        val n = e as Num                         #1
        return n.value
    }
    if (e is Sum) {
        return eval(e.right) + eval(e.left)      #2
    }
    throw IllegalArgumentException("Unknown expression")
}
 
fun main() {
    println(eval(Sum(Sum(Num(1), Num(2)), Num(4))))
    // 7
}

This version checks `e is Num` and then performs an explicit `e as Num` conversion before reading `value`; smart casts remove that redundant intermediate conversion in a checked branch. section 2.3.6

Use smart casts to make branching code both direct and safe:

  • Check a value with `is` before using members that belong only to the tested subtype. section 1.4.3
  • Keep subtype-specific work in the branch whose type check establishes that subtype. section 1.4.3
  • Apply the same branching approach to nullable values: after `s != null`, `s.length` is permitted; otherwise return an appropriate alternative. section 7.2
  • Use `when` when choosing among cases, where it serves as a more powerful alternative to Java-style `switch`. section 2.3

The result is idiomatic Kotlin that avoids boilerplate without abandoning runtime checks. Rather than treating casts as a separate, error-prone operation, let the type test establish what operations are safe in each path. section 1.4.3 section 2.0

How Kotlin in Action, 2E covers it

How deep it goes
Works through a recursive Expr evaluator and contrasts explicit casting with compiler-recognized branch typing.
Examples
Uses an Any value checked as String and an Expr tree of Num and Sum nodes evaluated to 7.
What it assumes
Introduces smart casts alongside Kotlin variables, functions, classes, properties, and control structures.
What you can do after
Apply it via Kotlin `is` checks to call subtype members without repeated casts.
How it connects
Connects type checks and casts with when expressions, nullable types, interfaces, and recursive evaluation.

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Kotlin in Action, 2E. The section links point to the parts of the book it draws on.