kotlingenerics

What is the difference between "x is T" and "(x as? T) != null"?


In Kotlin, when writing a generic, non-inline function, x is T throws a compiler error due to type erasure.

fun <T> example(foo: Any, bar: T) {
    if (foo is T) { // compile error
        // ...
    }
}

However x as? T is perfectly fine, it seems (although does throw the usual "unchecked cast" warning). Why can't the compiler just replace x is T with (x as? T) != null? Is there a difference between these?

fun <T> example(foo: Any, bar: T) {
    if ((foo as? T) != null) { // no compile error
        // ...
    }
}

Solution

  • This is explained in document Generics type checks and casts

    Due to the type erasure, there is no general way to check whether an instance of a generic type was created with certain type arguments at runtime, and the compiler prohibits such is-checks such as ints is List or list is T (type parameter).

    Explained why is is not allowed. And

    ... The type arguments of generic function calls are also only checked at compile time. Inside the function bodies, the type parameters cannot be used for type checks, and type casts to type parameters (foo as T) are unchecked

    state that foo as? T is unchecked, just like foo as? Any.

    We can verify this by running below

    public fun <T> asExample(foo: Any, bar: T): Boolean {
      if ((foo as? T) != null) { // no compile error
        return true
      }
      return false
    }
    
    public fun main() {
      println(asExample("A", "B"))
      println(asExample("A", 0))
    }
    

    Which will print

    true
    true
    

    Related: What is difference between "as" and "is" operator in Kotlin?