DEV Community

loading...

Improvements to inline classes in Kotlin 1.4.30

Harsh Shandilya
Full time Android developer in Kotlin and Java, experienced with Python and a work-in-progress Rustacean
Originally published at msfjarvis.dev on ・3 min read

What are inline classes?

Inline classes are a Kotlin language feature introduced in Kotlin 1.3 that allow creating no-cost 'wrapper' classes. This is best understood with an example.

Imagine that your project requires an EmailAddress type.

class EmailAddress(val value: String)
Enter fullscreen mode Exit fullscreen mode

This is fine, but there are hidden performance costs. Classes are initialized on the heap, and fields are much cheaper, fields of primitive types even more so. We only really need to be able to call some Strings an EmailAddress, so we can make this class inline.

inline class EmailAddress(val value: String)
Enter fullscreen mode Exit fullscreen mode

This will result in the EmailAddress wrapper being erased at runtime into direct access of the underlying value field. The rules around this are not as simple as "inline means it will always be inlined", and I recommend reading the representation of inline classes documentation to get a better idea of what particular situations the compiler can optimize.

Upcoming improvements for inline classes

With that small refresher on inline classes, let's talk about the restrictions that are being lifted in 1.4.30 and how they are helpful for us as developers.

init {} blocks are now allowed

This is pretty straightforward: you can now have initialization logic in inline classes. Let's re-use our email example but now try to perform some validation.

inline class EmailAddress(val value: String) {
  // 🎉 New in Kotlin 1.4.30
  init {
    require(value.isNotEmpty()) { "empty email address does not make sense" }
  }
}
Enter fullscreen mode Exit fullscreen mode

On Kotlin 1.4.20, this will fail to compile with Inline class cannot have an initializer block. This change is valuable because ultimately most of us will be wrapping primitive types into these classes and will frequently have restrictions on what is valid, so being able to validate is extremely helpful to catch invalid values early.

Primary constructor can be made non-public

The primary constructor of an inline class can now be made internal or private, which was previously impossible.

// Sample courtesy https://youtrack.jetbrains.com/issue/KT-28056#focus=Comments-27-4357288.0-0
inline class EmailAddress @PublishedApi
/* 🎉 New in Kotlin 1.4.30 */
internal constructor(private val value: String) {
  override fun toString(): String = value
  companion object {

    fun parse(string: String): EmailAddress {
      check(string.contains('@')) { "invalid email address." }
      return EmailAddress(string)
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Weeeeeeeeeeeeeeeeeeeeeeeeeee

Yeah. What's happening here is that we've made the constructor of EmailAddress internal to restrict its use to the current module, but annotated it with @PublishedApi which makes it legal to use in public inline functions. Read the docs for the @PublishedApi annotation to get a better idea about what this means for you, since it's out of scope here.

Doing this allows us to have an EmailAddress type that can be used to verify that a String is a valid email and then have that information be carried downstream as the value's type. Why is having the type important? Allow me to refer you to "Aiming for correctness with types", provided you have a free hour or so. It's worth it.

Discussion (0)