A second look at Kotlin inline classes

In my previous post, I’ve mentioned one of the upcoming features of Kotlin 1.3: the inline classes.

In essence, an inline class is a data class with a single property, but with a lower runtime footprint — under regular circumstances, your inline class is not instantiated, its property is used instead (JVM acts as if the class was “inlined”, hence the name).

I’m looking forward to using this feature because it can make the code more type-safe. Think about all the different usages of String in your app: usernames, tokens, phone numbers. With inline classes, you’ll be able to treat each use case as a separate type without any runtime overhead.

One thing I wasn’t so happy about was a need for manual “unwraping” the backing property of the inline class, i.e.:

inline class UserToken(value: String)
val token = UserToken("…")
val tokenAsString = token.value // works fine
val tokenAsString = token // but why can't I write this?

From my point of view that automatic conversion would be beneficial: I would be able to gradually introduce inline classes, without changing every use site.

I couldn’t figure out why would the Kotlin devs require such seemingly useless boilerplate, so I asked a question on the Kotlin Slack channel. The reason is so simple, I’m ashamed I didn’t figure it out myself.

Take a look at the first example from the inline classes KEEP document:

inline class UInt(private val value: Int) { … }

It makes sense to back the UInt with Int, but it doesn’t make sense to access the backing property directly. It would be the same as doing an unsigned int to int cast in C++, reinterpreting bytes of the underlying data.

I was so focused on my own use case, that I ignored another, fairly obvious one.

More goodies

Once you free yourself of the misconception that inline class is a one-to-one wrapper for the backing property, you’ll start seeing other benefits.

If you have some kind of data represented by a String, you might be tempted to define the domain-specific extension functions on a String itself:

fun String.toLatLng(): LatLng = …

This extension should be used only on Strings that represent a geohash, but you don’t have a way to specify that. Inline classes provide a natural way to scope such methods:

inline class GeoHash(val hash: String) {
fun toLatLng(): LatLng = …
}

Lessons learned

  • RTFM
  • Look further than the end of your nose
  • Don’t be afraid to ask

And what about my use case?

I still hope it will make its way into the language. Maybe as a typealias modifier, or as a special case of union type with a single type?

For now, I’ll just use the inline class.