Hi, everyone! Iām Svetlana Isakova,
a Developer Advocate at JetBrains. In this video, I wanna talk about one of the
new language features of the Kotlin 1.5 release: inline value classes. In simple words, an inline class can wrap
a value without any additional overhead. This feature has been available for some time, it was added in an experimental
state in Kotlin 1.2.30. And Kotlin 1.5 stabilizes this
functionality with some changes. Letās start with an example. I wanna use Duration API as an example, to
define a problem and discuss different solutions. Duration represents the amount of
time between two time instants. Without inline classes, Duration can be implemented either via
primitives, or using a regular class. Letās try primitives first. We need a function
that does something after a given timeout. How to pass a timeout parameter?
We can use primitives, like ālongā. However, it might be really
confusing on the call site: we pass an integer constant but we donāt
know immediately whether itās seconds, or milliseconds or i-donāt-know
minutes. Itās not a type-safe solution. We could sort of overload this function by adding
the time units to its name but itās too verbose. Defining a separate Duration class
solves the type safety problem. We can define auxiliary functions
like seconds or minutes to emphasize the time units. Itās no longer error-prone
to use the new greetAfterTimeout function. We have explicit units in the code, cool. The only problem of this approach is that an extra
object is allocated to store the timeout duration. After gaining type safety we lost performance. Inline value classes solve that. They
combine performance of primitive types and type safety of regular classes. Starting from 1.5 you define an inline class
differently: as a value class annotated with the @JvmInline annotation.
But the concept is the same. Under the hood, the compiler replaces
the `Duration` parameter with `Long`. That means no extra object is
allocated when you pass a value! In this example, the compiler
replaces the Duration argument value with the underlying long constant in the bytecode. In the next example, we use the nice āsecondsā
function which returns the Duration value, and the compiler also replaces it with
the corresponding constant under the hood. Thatās what I mean by saying that
inline value classes combine the performance of primitive types and
type safety of regular classes. No extra objects are allocated,
itās primitives under the hood. And explicit units in the code
demonstrate the type safety: duration is represented by a
separate type, itās not any number. The Kotlin standard library already defines
several useful inline value classes. First of all, note that the standard library
contains the experimental Duration class which should be used to express durations
for timeouts and for other needs. You canāt call the constructor directly, you
create Duration instances using auxiliary functions defined in Duration companion object,
like Duration.seconds or Duration.minutes. This class was available in an experimental state
since 1.3.50 and it stays experimental so far because it was recently significantly changed
both in the API and internal representation. Before, it stored the underlying value
as āDoubleā property, not āLongā. We used it as an example in the
presentation ā in real-life tasks, you donāt need to redefine it. The
library class Duration does the job. Other useful types implemented via inline value
classes mechanism are unsigned number types. For each primitive integer type, thereās a
corresponding unsigned type, like UInt, or ULong. They store non-negative values occupying the
same memory as their regular counterparts. Under the hood, each of them is
defined as an inline value class wrapper over the corresponding integer type. Like in this case, UInt is
a wrapper over an Int value. You can convert a regular integer value
to unsigned value by calling a function like toUByte() or toUInt(). Alternatively,
you can define a constant with the āuā tag. Arithmetic operations and
comparisons are supported for them. Unsigned types were available for a
long type in an experimental state, and now they become stable. But note that
array of unsigned types remains in Beta. Of course, you can define
your own inline value classes. You mark the class as a value class and
annotate it with the JvmInline annotation. An inline class can be a wrapper either for a
primitive or for any reference type like String. Inline class is a wrapper for only one
property, and this property should be read-only. Mutable vars arenāt allowed. As for regular classes,
inside inline value classes, you can define member functions and properties. Since inline value class is a wrapper over
one property, other properties are allowed only if they donāt have backing fields.
They should compute the value on each access, like in this example. Mutable properties
without backing fields are also allowed. Donāt confuse inline value
classes with inline functions. Member functions of an inline value class
donāt get inlined; they are different concepts. You can also define an init block
to provide the constructor logic. Inline value classes are supported
by the kotlinx.serialization library. You can mark an inline
value class as Serializable, and when encoding data to JSON format, you see only the underlying value
included, not the whole class. If a regular class is encoded, you see it in the
output as an extra class. Like in this example, you see that the color is represented
by a class having an RGB property. For inline classes, the value is encoded
directly. And decoded correspondingly. Here, we now defined Color
as an inline value class. And you can notice that the integer RGB
value is serialized directly, as ācolorā. Letās now glance at what happens under the hood. If you expect that an inline value class is
always replaced with the underlying value, thatās not correct.
The wrapper is not always eliminated in the bytecode, it happens only when possible. It
works very similarly to built-in primitive types. When you define a variable or
pass it directly to a function, its type gets replaced with the underlying value.
Here, during the compilation time, itās `Duration` but itās replaced with a
primitive type in the bytecode. If you store a ādurationā value
in a collection or pass it to a generic function, however, it gets boxed.
Boxing and unboxing are made automatically by the compiler, so you donāt need to think about it,
but itās useful to understand how it works. If a function takes an inline class
as a parameter, its name is mangled. That means the compiler adds
a suffix to its initial name, like for āgreetAfterTimeoutā in this example. That happens for two reasons:
first, that allows overloading a function to take two different inline value
classes that wrap the same value. Like in this example. We have
two different ārecordā functions taking āNameā and āPasswordā as parameters. Without mangling, they would have the
same JVM signature in the bytecode and such code wonāt compile. The second reason for mangling is to
refrain its accidental usage from Java. Kotlin usages are type-safe, you
can only pass the correct type. But if you use it from Java, you can have the same
confusion problems as when using primitive types. To prevent them, both these
invocations donāt compile in Java. When the function name is mangled,
you no longer can call it from Java. Java sees only the mangled name but canāt
call it because itās not a valid identifier. If you wanna use a function taking an inline
value class argument from Java, use a workaround. You can provide an explicit
JvmName for this function. That changes the underlying name in the bytecode and makes it visible by this
name and usable from Java. I wanna point out one peculiarity of
defining inline classes in the library. Imagine youāre a library author and you define
an inline class as a part of the public API. If you wanna later change the
type of the underlying value, itās a breaking change!
Since any function using this inline class is compiled to the one
using the underlying type, that breaks! Thatās what happens with Duration, and that demonstrates why it
wasnāt a part of the stable API. Such change is a breaking change. We do want to stabilize this
functionality soon, but as always, we need your help to battle-test it.
Please share your feedback and your use-cases! If you follow the language changes
and were using inline classes before, you might be surprised, why the syntax changes. It was āinline classā before, but now itās āvalue
classā annotated with the @JvmInline annotation. Why? The term inline class turned out to
be a bit confusing with inline functions. Some might expect that all members
of the inline class are inlined, which is not the case.
Or one could think that an inline class will always be replaced with the
underlying value, which is also not correct. Whatās important, Inline classes now
become a part of the bigger story: a case of a value class with
the specific optimization. You donāt need to know these details
to use the new syntax right now. But if you wanna learn more about value classes, please watch a separate video about this planned
functionality. Should be linked somewhere (here). If you wanna learn more details
about inline value classes please check the description in
our blog and in the documentation. Thank you for your attention, and letās Kotlin!
How can this be stable before Valhalla?
About time inline classes left experimental.
Just got one question. Is that
@JVMInline
required, and if so.. why? Can't the kotlin compiler deduce that from thevalue class
?