Reified, crossinline, noinline and inline everything?
This is a continuation of the previous blog post.
As promised this is the blog post where we’ll talk more about the mysterious inline, crossinline and not so confusing noinline and my most favorite, reified.
Inlining
What is inlining?
Let’s say you have a
1
2
3
4
5
6
7
8
9
10
open class Dog {
fun bark() {
// call printBarkMessage
printBarkMessage()
}
fun printBarkMessage() {
println("meow, ughhm woof?")
}
}
Inlining is a way to optimize compiled source code at runtime with the help of the JIT (just in time compiler) by replacing the invocations of the most often executed methods with their bodies.
When inline happens we have the following
1
2
3
4
5
6
7
8
9
10
open class Dog {
fun bark() {
// call printBarkMessage
println("meow, ughhm woof?") <---- inlining hpapens
}
fun printBarkMessage() {
println("meow, ughhm woof?")
}
}
If another class was to extend Dog then we have to make our printBarkMessage() open and override it and we have
1
2
3
4
5
class Husky : Dog() {
override fun printBarkMessage(){
println("woofz and more woofz")
}
}
then essentially when inlining happens, the compiler will be stuck with the
1
2
3
fun printBarkMessage() {
println("meow, ughhm woof?")
}
which would be wrong in this case, since the object was actually an instance of Husky.
Well this is Java’s world, which applies to Kotlin but with some smart decisions done by the Kotlin team.
If we were to add inline modifier to printBarkMessage and override the printBarkMessage in our Husky class we’d get
it suggests you to make it open, but even if you do
you can’t inline it, Kotlin has solved the problem for us but does it end here?
Not quite.
In case you want only some of the lambdas passed to an inline function to be inlined, you can mark some of your function parameters with the noinline modifier:
1
inline fun bragAboutHowIAmUsingLinux(arch: () -> Unit, noinline ubuntu: () -> Unit) { ... }
If we were to write this function
1
2
3
4
inline fun stopUsingJava(body: () -> Unit) {
val migrateToKotlinRunnable = Runnable { body() } <--- won't compile
migrateToKotlinRunnable.run()
}
but why it doesn’t compile?
First, an inline function that call the lambdas passed to it as parameters not directly from the function body, can’t return out of a nested lambda, first let’s take a look of a case where you can return
1
2
3
4
5
6
fun hasBitcoin(listOfNames: List<String>): Boolean {
listOfNames.forEach {
if (it == "Elon Musk") return true
}
return false
}
this isn’t an inlined function and it’s body is not copy pasted around as an inline would and the compiler knows about its return and it’s not nested inside another lambda (forEach is an only one) but forEach is a lambda, now how do we fix stopUsingJava?
1
2
3
4
inline fun stopUsingJava(crossinline body: () -> Unit) {
val migrateToKotlinRunnable = Runnable { body() }
migrateToKotlinRunnable.run()
}
Inlining explanations
Since body can’t do non local returns and the function is an inlined one, body has to be marked with crossinline, you requested to copy-paste the code of the block into a place that expects a value by doing so it can’t contain non-local control flow, what does this mean? You can’t use return.
Inlining does some pretty good optimizations when all functional type parameters are called directly or passed to other inline function/s and if at least one of your functional type parameters can be inlined, use noinline for the others.
Another use case is reified type parameters, which require you to use inline (you’re forced to inline them), but why?
For example
1
2
3
4
fun <T> String.toKotlinObject(): T {
val mapper = myCustomObjectMapper() //does not compile!
return mapper.mapPojoValue(this, T::class.java)
}
1
2
3
4
inline fun <reified T: Any> String.toKotlinObject(): T {
val mapper = myCustomObjectMapper()
return mapper.mapPojoValue(this, T::class.java)
}
Alright, what’s happening here?
You tell the compiler to copy the function’s body (bytecode) to every line the function is invoked from (also known as inlining) from there on when you combine an inline function with reified type, the compiler knows the actual type passed as a type argument so that it can modify the generated bytecode to use the corresponding class directly.
If you have a call like myVariableFromServer is T
becomes myVariableFromServer
is Husky in the bytecode (if the type argument is Husky).
But how does this happen actually?
You probably heard of the interface Type, which is the superinterface of all types in the JVM.
As you can see Class is implementing this interface
If you access an
1
Any::class.java.genericSuperClass
it returns a Type, but Type won’t do us any good since it only hold a name describing itself but we can cast the Type to a ParameterizedType.
and now we have an array of actual type arguments that hold the object representing the actualy type argument, we need the first object only.
Type
1
2
val type: Type?
get() = (this::class.java.genericSuperclass as ParameterizedType).actualTypeArguments.firstOrNull()
and now if we were to have the class information for a String it will print out
1
class java.lang.String
and that’s how reified was born.
We have learned about inlining and reified and now every time you think of passing a lambda to your function (apart from being forced to use inline when you need the type parameter) you think of inlining it huh? I don’t blame you, your brain tricks you into inlining it just as it did tricked me.
Why won’t you need to do that? As explained in the previous blog post
Additionaly JVM has limits of up to 64K bytecode instructions for a single method, careful how you tread with inlining, it can be something you’re proud of, with great abstraction comes great overhead, either for you as a programmer or for the compiler.
Hope you learned something new today. Back to writing Kotlin.
You can inline properties you know?
1
2
val foo: Fighters
inline get() = Fighters()
1
2
3
var system: OfADown
get() = ...
inline set(value) { ... }
or the whole property which marks both the getter and the setter as inlined
1
2
3
inline var tool: Tool
get() = ...
set(value) { ... }
also when you have an internal property value annotate it with
1
@PublishedApi
so that you can use it inside an inlined function.
And also a function, constructor and a class can be marked with @PublishedApi, then instead of internal they’re public for the inlined functions.
From today’s knowledge we can write a simple exercise for an infamous event provider.
1
2
3
4
5
6
7
8
9
10
11
12
object RxBus {
@PublishedApi
internal val publisher = PublishSubject<Any>()
fun publish(event: Any) {
publisher.onNext(event)
}
inline fun <reified T> listen(): Observable<T> = publisher.ofType(T::class.java)
}
P.S: sometimes I write good code