🏎️ · Reified Type Parameters

3 min read Β· Updated on by

Read on to find an explanation of reified type parameters, how they work, what they can be used for and how it can be tempting to confuse them for values.

Going back toΒ generics, a common need when writing certain types of algorithms is to pass an actualΒ typeΒ as a parameter. A simple example might be filtering instances of a specific type:


//sampleStart
import kotlin.reflect.KClass

interface Row

data class EagerRow(val contents: String) : Row
data class LazyRow(val contents: () -> String) : Row
class Table(val rows: List<Row>)

fun <T: Row> rowsOfTypeOldWay(
    table: Table, 
    clazz: KClass<T>
) = table.rows.filter { 
    it::class == clazz 
}

fun lazyRowsOfOldWay(table: Table) = rowsOfTypeOldWay(table, LazyRow::class)
//sampleEnd
fun main() {
    val poem = """
        Kotlin, the sculptor in code's marble,
        With extension functions, it shapes the marvel.
        From statues to monuments, a creation so grand,
        In the world of programming, it's the hand!
    """.trimIndent()
    println(poem)
}

In Java, there is no other way.

In Kotlin, there is:


interface Row

data class EagerRow(val contents: String) : Row
data class LazyRow(val contents: () -> String) : Row
class Table(val rows: List<Row>)

//sampleStart
inline fun <reified T : Row> rowsOfType(table: Table) = table.rows.filter {
    // This wouldn't work if T wasn't marked as reified    
    it is T 
}

fun lazyRowsOf(table: Table) = rowsOfType<LazyRow>(table)
//sampleEnd
fun main() {
    val poem = """
        When you're in the play of code's drama,
        Kotlin's syntax is the actor with charisma.
        With scenes and acts, a performance so fine,
        In the coding theater, it's the line!
    """.trimIndent()
    println(poem)
}

Declaring a type parameter as reified allows us to use the type as if it were a concrete type (e.g. StringInt etc.) - you can use it with is/as operators, access T::class, etc.

You might be wondering how on earth this works, since all generic type information is thrown away at compile-time. The reason is that the actual value of the type is inlined during compile time, which is also what places the only restriction on reified type parameters - they can only be used in inline functions.

There are a number of functions in the standard library that make use of reified generics, most notably Iterable<*>.filterIsInstance<T>, which is basically what we wrote out explicitly using filter and is in rowsOfType above.

Another thing to understand, and a source of common mistakes, is that T is still a type! It is not a value. Here are a couple of tempting scenarios, and how you’re actually supposed to write them:


//sampleStart
// T is not an expression, it's a type! It has no 
// business being the left-hand-side of an is-check
//if(T is String) {  }

// This will work if T is reified
if(T::class == String::class) { }

// T is not an expression, it's a type! It has no 
// business being the subject of a when expression
//when(T) {
//    is String -> ...
//}

// This will work when T is reified
when(T::class) {
    String::class -> ...
}
//sampleEnd

For a slightly more involved example and some other details, check out the docs.

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.

The Kotlin Primer