Whether you’re writing massive data-chomping processes that execute in the cloud, or apps running on low-powered cell phones, most developers want their code to run fast. And now, Kotlin’s new, experimental inline classes feature allows us to create the data types that we want without giving up the performance that we need!

In this new series of articles, we’re going to take a look at inline classes from top to bottom!

In this article, we’re going to explore what they are, how they work, and the trade-offs involved when choosing to use them. Then, in the upcoming articles, we’re going to look under the hood of inline classes to see exactly how they’re implemented, and explore how they interoperate with Java.

Keep in mind - this is an experimental feature, and it’s very actively being developed and refined. This article is currently based on inline classes as implemented in Kotlin 1.3-M1.

If you’d like to try them out, I also wrote a companion article that shows you how to enable them in your IDE so that you can start playing with inline classes and other Kotlin 1.3 features today!

Ready? Let’s dive in!

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 in your project management system. 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 mail scheduler, and this is what you see:

interface MailScheduler {
fun sendEmail(email: Email, delay: Int)
}

Looking at this function, you know you need to call it… but what arguments would you send to 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 - it still requires that the developer pays attention to the name, and sends an integer that represents the unit of time that the function expects. It’s not hard to imagine errors in calculations:

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 type Minutes! Consider how the following code can go a long way toward reducing bugs compared to the previous version:

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, we’re averse to creating simple types like these 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 new inline class feature is designed to do just that!

Let’s take a look!

Introducing Inline Classes

Inline classes are very simple to create - just use the keyword inline when defining your class:

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, non-inlined classes.

You can instantiate and use the inline class just like any other class. You’ll probably eventually need to reference the underlying value at some point in the code - usually at the boundary with another library or system. At that point, of course, you’d access value like you normally would with any other class.

Key Terms You Should Know

Inline classes wrap an underlying value. That value has a type, which we call the underlying type.

Why Inline Classes Can Perform Better

So, what is it about inline classes that can cause them to perform better than regular classes? 2

When you instantiate an inline class like this:

val period = Hours(24)

… the class isn’t actually instantiated in the compiled code! In fact, as far as the JVM is concerned, you’ve simply written this …

int period = 24;

As you can see, there’s no notion of Hours here in this compiled version of the code - it’s just assigning the underlying value to an int variable!

Similarly, when you use an inline class as the type of a function parameter:

fun wait(period: Hours) { /* ... */ }

… it can effectively compile down to this …

void wait(int period) { /* ... */ }

So, the underlying type and underlying value were inlined in our code. In other words, the compiled code just used an integer type, so we avoided the cost of creating and accessing an object on the heap.

But wait a minute!

Remember how the Hours class had a function called toMinutes() on it? Since the compiled code is using an int instead of an Hours object, what happens when you call toMinutes()? After all, int doesn’t have a function called toMinutes(), so how does this work?

And if we were to call Hours(24).toMinutes() in Kotlin, it can effectively compile down to just toMinutes(24).

Okay, so that that takes care of functions, but what about fields? What if we want Hours to include some other data on it besides the main underlying value?

Everything has its trade-offs, and this is one of them - an inline class can’t have any other fields other than the underlying value. Let’s explore the rest of them.

Trade-offs and Limitations

Now that we know that inline classes can be represented by their underlying value in the compiled code, we’re ready to understand the trade-offs and limitations that are involved when we use them.

First, an inline class must include an underlying value. This means that it needs a primary constructor that accepts the underlying value as a read-only property. You can name the underlying value whatever you want.

However, they can have properties, as long as they are calculated based only the underlying value, or from some value or object that can be statically resolved - from singletons, top-level objects, constants, and so on.

Type Aliases vs. Inline Classes

Because they both encompass an underlying type, inline classes can be confused with type aliases. But there are a few key differences that make them useful in different situations.

Type aliases provide an alternate name for the underlying type. For example, you can alias a common type like String, and give it a descriptive name that’s meaningful in a particular context, like Username. Variables of type Username are actually variables of type String in both the source code and the compiled code. For example, you can do this:

Notice how we can call .length on name. That’s because name is a String, even though the parameter type we declared is using the alias Username.

Inline classes, on the other hand, are wrappers of the underlying type, so when you need to use the underlying value, you have to unwrap it again. For example, let’s rewrite the code above using an inline class instead of a type alias:

In this case, Username and Password are just different names for String, so you can assign a Username to a Password and vice versa. In fact, that’s exactly what we did in the code above - we mixed up the username and password when we called authenticate()… and the compiler was just fine with this!

On the other hand, if you use inline classes for this same case, then we’d thankfully get a compiler error:

Wrap-up

Although we’ve covered the basics, there are some fascinating considerations to keep in mind when using them. In fact, if you aren’t aware of what’s happening inside, inline classes could produce code that runs more slowly than normal classes.

In the next article, we’ll pop open the hood and look inside to get an in-depth understanding of exactly how they work, so that you can get the best possible use out of them.

See you then!

You might also be wondering about local variables that are objects, and objects that contain primitives. In the first case, the object is stored on the heap, and a reference to it is stored on the stack. In the second case, the primitive is stored within the object on the heap.
[return]

There are lots of conditions that can affect their performance. In this article, we’re focused on introducing the concept, so we’ll stick with the best inlining scenario. The next article will cover the edge cases.
[return]