What’s New in Kotlin 1.3

Coroutines release

After some long and extensive battle testing, coroutines are now released! It means that from Kotlin 1.3 the language support and the API are fully stable. Check out the new coroutines overview page.

Kotlin 1.3 introduces callable references on suspend-functions and support of Coroutines in the Reflection API.

Kotlin/Native

Kotlin 1.3 continues to improve and polish the Native target. See the Kotlin/Native overview for details.

Multiplatform Projects

In 1.3, we’ve completely reworked the model of multiplatform projects in order to improve expressiveness and flexibility, and to make sharing common code easier. Also, Kotlin/Native is now supported as one of the targets!

The key differences to the old model are:

  • In the old model, common and platform-specific code needed to be placed in separate modules, linked by expectedBy dependencies. Now, common and platform-specific code is placed in different source roots of the same module, making projects easier to configure.
  • There is now a large number of preset platform configurations for different supported platforms.
  • The dependencies configuration has been changed; dependencies are now specified separately for each source root.
  • Source sets can now be shared between an arbitrary subset of platforms (for example, in a module that targets JS, Android and iOS, you can have a source set that is shared only between Android and iOS).
  • Publishing multiplatform libraries is now supported.

For more information, please refer to the Multiplatform Programming documentation.

Contracts

The Kotlin compiler does extensive static analysis to provide warnings and reduce boilerplate. One of the most notable features is smartcasts — with the ability to perform a cast automatically based on the performed type checks:

  1. fun foo(s: String?) {
  2. if (s != null) s.length // Compiler automatically casts 's' to 'String'
  3. }

However, as soon as these checks are extracted in a separate function, all the smartcasts immediately disappear:

  1. fun String?.isNotNull(): Boolean = this != null
  2. fun foo(s: String?) {
  3. if (s.isNotNull()) s.length // No smartcast :(
  4. }

To improve the behavior in such cases, Kotlin 1.3 introduces experimental mechanism called contracts.

Contracts allow a function to explicitly describe its behavior in a way which is understood by the compiler. Currently, two wide classes of cases are supported:

  • Improving smartcasts analysis by declaring the relation between a function’s call outcome and the passed arguments values:
  1. fun require(condition: Boolean) {
  2. // This is a syntax form which tells the compiler:
  3. // "if this function returns successfully, then the passed 'condition' is true"
  4. contract { returns() implies condition }
  5. if (!condition) throw IllegalArgumentException(...)
  6. }
  7. fun foo(s: String?) {
  8. require(s is String)
  9. // s is smartcast to 'String' here, because otherwise
  10. // 'require' would have thrown an exception
  11. }
  • Improving the variable initialization analysis in the presence of high-order functions:
  1. fun synchronize(lock: Any?, block: () -> Unit) {
  2. // It tells the compiler:
  3. // "This function will invoke 'block' here and now, and exactly one time"
  4. contract { callsInPlace(block, EXACTLY_ONCE) }
  5. }
  6. fun foo() {
  7. val x: Int
  8. synchronize(lock) {
  9. x = 42 // Compiler knows that lambda passed to 'synchronize' is called
  10. // exactly once, so no reassignment is reported
  11. }
  12. println(x) // Compiler knows that lambda will be definitely called, performing
  13. // initialization, so 'x' is considered to be initialized here
  14. }

Contracts in stdlib

stdlib already makes use of contracts, which leads to improvements in the analyses described above. This part of contracts is stable, meaning that you can benefit from the improved analysis right now without any additional opt-ins:

  1. //sampleStart
  2. fun bar(x: String?) {
  3. if (!x.isNullOrEmpty()) {
  4. println("length of '$x' is ${x.length}") // Yay, smartcast to not-null!
  5. }
  6. }
  7. //sampleEnd
  8. fun main() {
  9. bar(null)
  10. bar("42")
  11. }

Custom Contracts

It is possible to declare contracts for your own functions, but this feature is experimental, as the current syntax is in a state of early prototype and will most probably be changed. Also please note that currently the Kotlin compiler does not verify contracts, so it’s the responsibility of the programmer to write correct and sound contracts.

Custom contracts are introduced by a call to contract stdlib function, which provides DSL scope:

  1. fun String?.isNullOrEmpty(): Boolean {
  2. contract {
  3. returns(false) implies (this@isNullOrEmpty != null)
  4. }
  5. return this == null || isEmpty()
  6. }

See the details on the syntax as well as the compatibility notice in the KEEP.

Capturing when subject in a variable

In Kotlin 1.3, it is now possible to capture the when subject into variable:

  1. fun Request.getBody() =
  2. when (val response = executeRequest()) {
  3. is Success -> response.body
  4. is HttpError -> throw HttpException(response.status)
  5. }

While it was already possible to extract this variable just before when , val in when has its scope properly restricted to the body of when, and so preventing namespace pollution. See the full documentation on when here.

@JvmStatic and @JvmField in companion of interfaces

With Kotlin 1.3, it is possible to mark members of a companion object of interfaces with annotations @JvmStatic and @JvmField. In the classfile, such members will be lifted to the corresponding interface and marked as static.

For example, the following Kotlin code:

  1. interface Foo {
  2. companion object {
  3. @JvmField
  4. val answer: Int = 42
  5. @JvmStatic
  6. fun sayHello() {
  7. println("Hello, world!")
  8. }
  9. }
  10. }

It is equivalent to this Java code:

  1. interface Foo {
  2. public static int answer = 42;
  3. public static void sayHello() {
  4. // ...
  5. }
  6. }

Nested declarations in annotation classes

In Kotlin 1.3 it is possible for annotations to have nested classes, interfaces, objects, and companions:

  1. annotation class Foo {
  2. enum class Direction { UP, DOWN, LEFT, RIGHT }
  3. annotation class Bar
  4. companion object {
  5. fun foo(): Int = 42
  6. val bar: Int = 42
  7. }
  8. }

Parameterless main

By convention, the entry point of a Kotlin program is a function with a signature like main(args: Array<String>), where args represent the command-line arguments passed to the program. However, not every application supports command-line arguments, so this parameter often ends up not being used.

Kotlin 1.3 introduced a simpler form of main which takes no parameters. Now “Hello, World” in Kotlin is 19 characters shorter!

  1. fun main() {
  2. println("Hello, world!")
  3. }

Functions with big arity

In Kotlin, functional types are represented as generic classes taking a different number of parameters: Function0<R>, Function1<P0, R>, Function2<P0, P1, R>, … This approach has a problem in that this list is finite, and it currently ends with Function22.

Kotlin 1.3 relaxes this limitation and adds support for functions with bigger arity:

  1. fun trueEnterpriseComesToKotlin(block: (Any, Any, ... /* 42 more */, Any) -> Any) {
  2. block(Any(), Any(), ..., Any())
  3. }

Progressive mode

Kotlin cares a lot about stability and backward compatibility of code: Kotlin compatibility policy says that “breaking changes” (e.g., a change which makes the code that used to compile fine, not compile anymore) can be introduced only in the major releases (1.2, 1.3, etc.).

We believe that a lot of users could use a much faster cycle, where critical compiler bug fixes arrive immediately, making the code more safe and correct. So, Kotlin 1.3 introduces progressive compiler mode, which can be enabled by passing the argument -progressive to the compiler.

In progressive mode, some fixes in language semantics can arrive immediately. All these fixes have two important properties:

  • they preserve backward-compatibility of source code with older compilers, meaning that all the code which is compilable by the progressive compiler will be compiled fine by non-progressive one.
  • they only make code safer in some sense — e.g., some unsound smartcast can be forbidden, behavior of the generated code may be changed to be more predictable/stable, and so on.

Enabling the progressive mode can require you to rewrite some of your code, but it shouldn’t be too much — all the fixes which are enabled under progressive are carefully handpicked, reviewed, and provided with tooling migration assistance. We expect that the progressive mode will be a nice choice for any actively maintained codebases which are updated to the latest language versions quickly.

Inline classes

Inline classes are available only since Kotlin 1.3 and currently are in Alpha. See details in the reference.

Kotlin 1.3 introduces a new kind of declaration — inline class. Inline classes can be viewed as a restricted version of the usual classes, in particular, inline classes must have exactly one property:

  1. inline class Name(val s: String)

The Kotlin compiler will use this restriction to aggressively optimize runtime representation of inline classes and substitute their instances with the value of the underlying property where possible removing constructor calls, GC pressure, and enabling other optimizations:

  1. inline class Name(val s: String)
  2. //sampleStart
  3. fun main() {
  4. // In the next line no constructor call happens, and
  5. // at the runtime 'name' contains just string "Kotlin"
  6. val name = Name("Kotlin")
  7. println(name.s)
  8. }
  9. //sampleEnd

See reference for inline classes for details.

Unsigned integers

Unsigned integers are available only since Kotlin 1.3 and currently are in Beta. See details in the reference.

Kotlin 1.3 introduces unsigned integer types:

  • kotlin.UByte: an unsigned 8-bit integer, ranges from 0 to 255
  • kotlin.UShort: an unsigned 16-bit integer, ranges from 0 to 65535
  • kotlin.UInt: an unsigned 32-bit integer, ranges from 0 to 2^32 - 1
  • kotlin.ULong: an unsigned 64-bit integer, ranges from 0 to 2^64 - 1

Most of the functionality of signed types are supported for unsigned counterparts too:

  1. fun main() {
  2. //sampleStart
  3. // You can define unsigned types using literal suffixes
  4. val uint = 42u
  5. val ulong = 42uL
  6. val ubyte: UByte = 255u
  7. // You can convert signed types to unsigned and vice versa via stdlib extensions:
  8. val int = uint.toInt()
  9. val byte = ubyte.toByte()
  10. val ulong2 = byte.toULong()
  11. // Unsigned types support similar operators:
  12. val x = 20u + 22u
  13. val y = 1u shl 8
  14. val z = "128".toUByte()
  15. val range = 1u..5u
  16. //sampleEnd
  17. println("ubyte: $ubyte, byte: $byte, ulong2: $ulong2")
  18. println("x: $x, y: $y, z: $z, range: $range")
  19. }

See reference for details.

@JvmDefault

@JvmDefault is only available since Kotlin 1.3 and currently is experimental. See details in the reference page.

Kotlin targets a wide range of the Java versions, including Java 6 and Java 7, where default methods in the interfaces are not allowed. For your convenience, the Kotlin compiler works around that limitation, but this workaround isn’t compatible with the default methods, introduced in Java 8.

This could be an issue for Java-interoperability, so Kotlin 1.3 introduces the @JvmDefault annotation. Methods, annotated with this annotation will be generated as default methods for JVM:

  1. interface Foo {
  2. // Will be generated as 'default' method
  3. @JvmDefault
  4. fun foo(): Int = 42
  5. }

Warning! Annotating your API with @JvmDefault has serious implications on binary compatibility. Make sure to carefully read the reference page before using @JvmDefault in production.

Standard library

Multiplatform Random

Prior to Kotlin 1.3, there was no uniform way to generate random numbers on all platforms — we had to resort to platform specific solutions, like java.util.Random on JVM. This release fixes this issue by introducing the class kotlin.random.Random, which is available on all platforms:

  1. import kotlin.random.Random
  2. fun main() {
  3. //sampleStart
  4. val number = Random.nextInt(42) // number is in range [0, limit)
  5. println(number)
  6. //sampleEnd
  7. }

isNullOrEmpty/orEmpty extensions

isNullOrEmpty and orEmpty extensions for some types are already present in stdlib . The first one returns true if the receiver is null or empty, and the second one falls back to an empty instance if the receiver is null. Kotlin 1.3 provides similar extensions on collections, maps, and arrays of objects.

Copying elements between two existing arrays

The array.copyInto(targetArray, targetOffset, startIndex, endIndex) functions for the existing array types, including the unsigned arrays, make it easier to implement array-based containers in pure Kotlin.

  1. fun main() {
  2. //sampleStart
  3. val sourceArr = arrayOf("k", "o", "t", "l", "i", "n")
  4. val targetArr = sourceArr.copyInto(arrayOfNulls<String>(6), 3, startIndex = 3, endIndex = 6)
  5. println(targetArr.contentToString())
  6. sourceArr.copyInto(targetArr, startIndex = 0, endIndex = 3)
  7. println(targetArr.contentToString())
  8. //sampleEnd
  9. }

associateWith

It is quite a common situation to have a list of keys and want to build a map by associating each of these keys with some value. It was possible to do it before with the associate { it to getValue(it) } function, but now we’re introducing a more efficient and easy to explore alternative: keys.associateWith { getValue(it) }.

  1. fun main() {
  2. //sampleStart
  3. val keys = 'a'..'f'
  4. val map = keys.associateWith { it.toString().repeat(5).capitalize() }
  5. map.forEach { println(it) }
  6. //sampleEnd
  7. }

ifEmpty and ifBlank functions

Collections, maps, object arrays, char sequences, and sequences now have an ifEmpty function, which allows specifying a fallback value that will be used instead of the receiver if it is empty:

  1. fun main() {
  2. //sampleStart
  3. fun printAllUppercase(data: List<String>) {
  4. val result = data
  5. .filter { it.all { c -> c.isUpperCase() } }
  6. .ifEmpty { listOf("<no uppercase>") }
  7. result.forEach { println(it) }
  8. }
  9. printAllUppercase(listOf("foo", "Bar"))
  10. printAllUppercase(listOf("FOO", "BAR"))
  11. //sampleEnd
  12. }

Char sequences and strings in addition have an ifBlank extension that does the same thing as ifEmpty, but checks for a string being all whitespace instead of empty.

  1. fun main() {
  2. //sampleStart
  3. val s = " \n"
  4. println(s.ifBlank { "<blank>" })
  5. println(s.ifBlank { null })
  6. //sampleEnd
  7. }

Sealed classes in reflection

We’ve added a new API to kotlin-reflect that can be used to enumerate all the direct subtypes of a sealed class, namely KClass.sealedSubclasses.

Smaller changes

  • Boolean type now has companion.
  • Any?.hashCode() extension, which returns 0 for null.
  • Char now provides MIN_VALUE/MAX_VALUE constants.
  • SIZE_BYTES and SIZE_BITS constants in primitive type companions.

Tooling

Code Style Support in IDE

Kotlin 1.3 introduces support for the recommended code style in the IDE. Check out this page for the migration guidelines.

kotlinx.serialization

kotlinx.serialization is a library which provides multiplatform support for (de)serializing objects in Kotlin. Previously, it was a separate project, but since Kotlin 1.3, it ships with the Kotlin compiler distribution on par with the other compiler plugins. The main difference is that you don’t need to manually watch out for the Serialization IDE Plugin being compatible with the Kotlin IDE Plugin version you’re using: now the Kotlin IDE Plugin already includes serialization!

See here for details.

Please, note, that even though kotlinx.serialization now ships with the Kotlin Compiler distribution, it is still considered to be an experimental feature in Kotlin 1.3.

Scripting update

Please note, that scripting is an experimental feature, meaning that no compatibility guarantees on the API are given.

Kotlin 1.3 continues to evolve and improve scripting API, introducing some experimental support for scripts customization, such as adding external properties, providing static or dynamic dependencies, and so on.

For additional details, please consult the KEEP-75.

Scratches support

Kotlin 1.3 introduces support for runnable Kotlin scratch files. Scratch file is a kotlin script file with a .kts extension which you can run and get evaluation results directly in the editor.

Consult the general Scratches documentation for details.