Whether you’re writing massive data-chomping processes that run in the cloud, or mobile apps that run on low-powered cell phones, most of us want our code to run fast. And now, Kotlin’s inline classes feature allows us to create the data types that we want without giving up the performance that we need!
In this series, we’re going to take a look at inline classes from top to bottom!
This article explores what they are, how they work, and the trade-offs involved when choosing to use them. Then, the next article will look under the hood of inline classes to see exactly how they’re implemented.
By the way, an inline class is a special kind of value class, so although you’ll usually find them called “inline classes”, you might sometimes also find the term “inline value classes”. In this article, I’ll refer to them simply as “inline classes”.
Ready to dive in? Let’s go!
Strong Types and Simple Values: A Case for Inline Classes
It’s 8 AM on Monday morning, and after pouring yourself a fresh, steaming cup o’ joe, you pull a new ticket to work on. It reads:
Send new users a welcome email - four days after they sign up.
Since the mailing system has already been written, you pull up the interface for the mail scheduler, and this is what you see:
interface MailScheduler {
fun sendEmail(email: Email, delay: Int)
}
You know you need to call this function… but what arguments should you send it in order to delay the email by 4 days?
The delay
parameter has a type of Int
. So, we know that it’s an integer, but we don’t know what unit it represents - should you pass 4
for 4 days? Or maybe it represents hours, in which case you should pass 96
. Or maybe it’s in minutes, seconds or milliseconds…
How could we improve this code?
Sure, we can change the name of the parameter to include the time unit, like delayInMinutes
. That would certainly be an improvement. But even this is only a hint - the developer still has to pay attention to the name, and send an integer that represents the unit of time that the function expects. It’s not hard to imagine errors in calculations:
fun send(email: Email) {
mailScheduler.sendEmail(email, WELCOME_EMAIL_DELAY_IN_DAYS * 24 * 60 * 60)
}
Oops! We accidentally converted it to seconds instead of minutes!
What would make this even better?
If the compiler could enforce that the right unit of time is sent. For example, instead of accepting an Int
, let’s update our interface so that it accepts a strong type - Minutes
:
interface MailScheduler { fun sendEmail(email: Email, delay: Minutes) }
Now we’ve got the type system working for us! We can’t possibly send a
Seconds
to this function because it only accepts an argument of typeMinutes
! Consider how the following code can go a long way toward reducing bugs compared to the previous version:val defaultDelay = Days(4) fun send(email: Email) { mailScheduler.sendEmail(email, defaultDelay.toMinutes()) }
We get a lot more assurance when we can take full advantage of the type system.
But developers often choose not to write these kinds of simple value wrapper classes, opting instead to pass integers, floats, and booleans around the code base.
Why is that?
Often it’s because of performance reasons. As you might recall, memory on a JVM looks a little something like this:
When we create a local variable (that is, function parameters and variables defined within a function) of a primitive type - like integers, floating point numbers, and booleans - those values are stored on a part of the JVM memory called the stack. There’s not very much overhead involved in storing the bits for these primitive values on the stack.
On the other hand, whenever we instantiate an object, that object is stored on the heap1. We take a performance hit when storing and using objects - heap allocations and memory fetches are expensive. Per object, the cost is small, but when accumulated, it can have an important impact on how fast your code runs.
Wouldn’t it be great if we could get all the benefits of the type system without taking the performance hit?
In fact, Kotlin’s inline class feature lets us do just that!
Let’s take a look!
Introducing Inline Classes
Inline classes are very simple to create - just use the keyword
value
when defining your class, and if you’re targeting the JVM, add the annotation@JvmInline
, like this:@JvmInline value class Hours(val value: Int)
That’s it! This class will now serve as a strong type for your value, and in many cases, it won’t have nearly the same performance costs that you’d experience with regular classes.