Search code examples
kotlinkotlin-reified-type-parameters

Why have the reified keyword in Kotlin, isn't marking a function inline sufficient?


In Kotlin, given that the 'reified' keyword can only be used for generic type parameters of inline functions, why have the reified keyword at all? Why can't the Kotlin compiler (at least in the future) automatically consider all generic type parameters of inline functions as reified?

I have seen that people panic when looking at this 'reified' word and ask me not to make the code complex. Hence the question.


Solution

  • Reified type parameters are requiring type arguments passed in them to be reified as well. Sometimes it's an impossible requirement (for instance, class parameters can't be reified), so making all parameters of inline functions reified by default would make it impossible to call ALL inline functions in cases when now it's only impossible to call ones with reified type parameters:

    inline fun<T> genericFun(x: T)  {}
    inline fun<reified T> reifiedGenericFun(x: T)  {}
    
    class SimpleGenericClass<T>() {
        fun f(x: T) {
            genericFun<T>(x)        //compiles fine
            reifiedGenericFun<T>(x) //compilation error
        }
    }
    

    UPDATE. Why not automatically infer "reifibility" based on the context?

    1. Approach 1 (suggested by @Tenfour04): Analyze code of inlined function and consider its type parameter as reified if it has T::class calls (I'd also added is T calls).
    2. Approach 2 (suggested by @SillyQuestion): Consider all type parameters of inline functions as reified by default; if it leads to compilation error on usage site, then fallback to non-reified type.

    Here is a counter-example to both: "a" as? T. Function with this body would have different semantics depending on whether or not its type parameter is declared (or, hypothetically, inferred) as reified:

    inline fun<reified T> castToReifiedGenericType() = "a" as? T
    inline fun<T> castToSimpleGenericType() = "a" as? T
    
    fun main() {
        println(castToReifiedGenericType<Int>()) //null
        println(castToSimpleGenericType<Int>())  //a
    }
    
    /*P.S. unsafe cast ("a" as T) also have different semantics for reified and non-reified T, 
    causing `ClassCastException` in the first case and still returning "a" in the latter.*/
    

    So, with the first approach, semantics would change if we add a meaningless call to T::class/is T somewhere inside the inline function. With second - semantics would change if we call this function from the new site (where T can't be reified, while it was "reifiable" before), or, сonversely, remove a call from this site (allowing it to be reified).

    Debugging problems coming from these actions (at first glance unrelated to observing semantic changes) is way more complex and panic-inducing, than adding/reading an explicit reified keyword.