What to Expect in Kotlin 1.4 and Beyond

Posted on by

During the keynote at KotlinConf, Andrey highlighted our strategic view on the current areas of focus for the evolution of Kotlin, and our plans for Kotlin 1.4 which will be released some time next year.

Watch the entire keynote below

Our vision is for Kotlin to be a reliable companion for all your endeavors, a default language choice for your tasks. To accomplish this, we’re going to make it shine on all platforms. Multiple case studies from companies well-known in the industry show that we are making good progress in this direction.

Kotlin 1.4 that is going to arrive in spring 2020 will make another step forward for the Kotlin ecosystem.

Focusing on quality

Most of all, Kotlin 1.4 will focus on quality and performance. Kotlin is a modern language that already pioneers many ideas and approaches. We’re going to keep it modern and always evolving. At the moment, however, we believe that Kotlin has reached the stage where improving the overall experience is more important than adding big features. This is why Kotlin 1.4 will deliver only a few small language changes, which are explained in detail below.

We’ve already managed to achieve some impressive results in boosting the performance of IDEs that support Kotlin. The code completion speed has increased substantially compared to previous versions:

Together with the Gradle team, we’ve made Gradle scripts faster. Kotlin 1.3.60, Gradle Import in Android Studio is about 2.5 times as fast and takes about 75% less memory than it did in Kotlin 1.3.10:

What’s more, loading build.gradle.kts entails almost zero CPU usage! Also, compiling Kotlin/Native in dev mode is becoming up to 2 times as fast with code caching.

We understand that build speed is often the biggest concern for our users, and we are constantly improving our toolchain to address that. But incremental improvements cannot keep up with the natural growth of production codebases: while we make compilation faster, users write more code, and the overall build time doesn’t improve enough. It’s become clear that we need to reimplement the compiler to make it really fast.

New compiler

The goal for the new compiler implementation is to be really fast, unify all the platforms Kotlin supports, and provide an API for compiler extensions. This is going to be a multi-year effort, but we started a while ago, so some parts of this new implementation are going to arrive in 1.4, and the transition will be very gradual. It’s happening already; for instance, if you’ve tried the new algorithm for type inference, that’s a part of the new compiler. The approach for other parts will be the same; that is, both versions will be available for some time, the old one and the new one in an experimental mode; and when the new one becomes stable, it will become the default.

Speed-up with the new front-end

The bulk of the speed-up we expect from the new compiler will come through a new front-end implementation.

To provide a little background, compilation can be thought of as a pipeline that takes source files and turns them into executable code step by step. The first big step in this pipeline is colloquially referred to as the front-end of the compiler. It parses the code, resolves names, performs type checking, etc. This part of the compiler also works inside the IDE when it highlights errors, navigates to definitions, and searches for symbol usages in your project. And this is the step where kotlinc spends the most time nowadays, so we want to make it much faster.

The current implementation is not complete yet, and it will not arrive in 1.4. However, it already does most of the time-consuming work, and we can measure the expected speed-up. Our benchmarks (compiling YouTrack and the Kotlin compiler itself) show that the new front-end will be about 4.5 times as fast as the existing one.

Unified back-ends and extensibility

After the front-end is done analyzing the code, a back-end generates the executables. We have three back-ends: Kotlin/JVM, Kotlin/JS, and Kotlin/Native. The first two were historically written independently and didn’t share much code. When we started Kotlin/Native, it was based on a new infrastructure built around an internal representation (IR) for Kotlin code which serves a function somewhat similar to bytecode in virtual machines. We are now migrating the other two back-ends to the same IR. As a result, we will share a lot of the back-end logic and have a unified pipeline, to allow most features, optimizations, and bugfixes to be done only once for all targets.

We will gradually migrate to the new back-ends, and in 1.4 they are unlikely to be enabled by default, but users will be able to opt into using them explicitly.

A common back-end infrastructure opens the door for multiplatform compiler extensions. One can plug into the pipeline and add some custom processing and/or transformations which will automatically work for all targets. In 1.4 we do not provide a public API for such extensions (the API will be stabilized later), but we are working closely with our partners, including JetPack Compose, who are building their compiler plugins already.

Meet KLib: Kotlin Library Format

To build a multiplatform library in Kotlin and ship it so that clients can depend on it, one needs a distribution format that works equally on any platform. This is why we’re introducing KLib: a library format for Kotlin multiplatform. A KLib file contains serialized IR. Your code may add it as a dependency, and the compiler back-end will pick it up and generate executable code for the given platform. The analogy with bytecode still holds here: one can analyze and transform KLibs much like JVM bytecode. Any transformations done to the serialized IR will affect any platform the KLib will be used for.

In fact, Kotlin/Native has been using the KLibs format to distribute Kotlin native libraries for quite a while, and now we are extending the format to support other back-ends and multiplatform libraries. The format will be experimental in 1.4, and we will work on providing a stable ABI for it in future versions.

More multiplatform news

Running iOS code in Android Studio

We are working on a plugin for Android Studio that will be able to run, test, and debug Kotlin code on iOS devices and simulators. The plugin is using proprietary code from IntelliJ, so it will be closed-source. It will not bring language support for Objective-C or Swift, and some operations such as deployment to AppStore may require running Xcode, but anything you do with Kotlin code will work from Android Studio with the new plugin installed. We expect to open a preview for this plugin in 2020.

Kotlin/Native runtime improvements

Apart from Linux, Windows, macOS, and iOS, Kotlin/Native now works on watchOS and tvOS, so virtually any device can run Kotlin. We are also working on the runtime performance of Kotlin/Native to make iOS Kotlin programs run even faster.

Core libraries

The Kotlin core libraries work on all platforms. This includes kotlin-stdlib which handles all the basic types and collections, kotlinx.coroutines , kotlinx.serialization , and kotlinx.io . The support for dates is really needed in the multiplatform world, and this is what we are working on: experimental Durations have been already added to stdlib, and DateTime support is under way.

Another important addition to Kotlin libraries is Flow which is a coroutine-based implementation of Reactive Streams. Flow is great at processing streams of data, and it’s making use of the power of Kotlin in doing so. Apart from its ergonomics, Flow brings extra speed. On some benchmarks it is almost 2 times as fast as existing popular Reactive Streams implementations.

For library authors

As creating new libraries is vital for the Kotlin ecosystem, we keep improving the experience of library authors. The new library authoring mode will help shape your code in the way that’s best for stable APIs. Also, we are going to release Dokka 1.0 to support docs generation for all platforms.

Multiplatform web

Sharing code across platforms is great for mobile, but it’s also great for Web clients: a lot can be shared with the server and/or with mobile apps. We invest more and more in the Kotlin/JS tooling, and now can do very fast development roundtrips, from changing Kotlin code to seeing results in the browser:

We’ve also improved JS interop so that now you’ll be able to attach an NPM dependency to a Kotlin project and any .d.ts type definitions will be picked up automatically by the Kotlin toolchain.

The new IR-based back-end will also bring significant improvements in binary sizes. Compiled JS files can become half their current size.

New language features

Kotlin 1.4 will deliver a few new language features.

SAM conversions for Kotlin classes

The community has requested us to introduce support for SAM conversions for Kotlin classes (KT-7770). SAM conversion applies if you pass a lambda as an argument when an interface or a class with only one single abstract method is expected as a parameter. Then the compiler automatically converts the lambda to an instance of the class implementing the abstract member function.

SAM conversions currently only work for Java interfaces and abstract classes. The initial idea behind this design was to use function types explicitly for such use-cases. It turned out, however, that function types and typealiases don’t cover all the use-cases, and people often had to keep an interface in Java only to get a SAM-conversion for it.

Unlike Java, Kotlin will not allow a SAM conversion for every interface with one single abstract method. We believe that an intention to make an interface applicable for SAM conversion should be explicit. Thus, to define a SAM interface, you’ll need to mark an interface with the fun keyword to emphasize that it can be used as a functional interface:

fun interface Action { fun run() } fun runAction(a: Action) = a.run() fun main() { runAction { println("Hello, KotlinConf!") } }

Note that passing a lambda instead of a fun interface will be supported only in a new type inference algorithm.

Mixing named and positional arguments

Kotlin prohibits mixing arguments with explicit names (“named”) and regular ones without names (“positional”) unless you put named arguments only after all the positional ones. In one case, however, it’s really annoying: when all arguments stay in their correct positions and you want to specify a name for one argument in the middle. Kotlin 1.4 will fix this issue, so you will be able to write code like:

fun f(a: Int, b: Int, c: Int) {} fun main() { f(1, b = 2, 3) }

Optimized delegated properties

We’ll improve the underlying way in which the lazy property and some other delegated properties are compiled.

Generally, a delegated property can access the corresponding KProperty reflection object. For instance, when using Delegates.observable , you can display information about the modified property:

import kotlin.properties.Delegates class MyClass { var myProp: String by Delegates.observable("<no name>") { kProperty, oldValue, newValue -> println("${kProperty.name}: $oldValue -> $newValue") } } fun main() { val user = MyClass() user.myProp = "first" user.myProp = "second" }

To make this possible, the Kotlin compiler generates an additional syntactic member property, an array storing all KProperty objects that represent delegated properties used inside the class:

>>> javap MyClass public final class MyClass { static final kotlin.reflect.KProperty[] $$delegatedProperties; ... }

Some delegated properties, however, don’t use KProperty in any way. For them, generating an object in $$delegatedProperties is suboptimal. The Kotlin 1.4 release will optimize such cases. If the delegated property operators are inline , and the KProperty parameter is not used, the corresponding reflection objects will not be generated.

The most outstanding example is the lazy property. The implementation of getValue for the lazy property is inline and doesn’t use the KProperty parameter:

inline operator fun <T> Lazy<T>.getValue(thisRef: Any?, property: KProperty<*>): T = value

Starting with Kotlin 1.4, when you define a lazy property, the corresponding KProperty instance won’t be generated. If the only delegated properties you use in a class are lazy properties (and other properties that comply with this optimization), the whole $$delegatedProperties array won’t be generated for the class:

class MyOtherClass { val lazyProp by lazy { 42 } } >>> javap MyOtherClass public final class MyOtherClass { // no longer generated: static final kotlin.reflect.KProperty[] $$delegatedProperties; ... }

Trailing Commas

This minor syntactic change turns out to be incredibly convenient! You can place an additional trailing comma after the last parameter in a parameter list. You can then swap lines or add new parameters without having to add or remove the missing commas.

Other notable changes

A useful typeof function introduced in Kotlin 1.3.40 is going to become stable and supported on all platforms.

The feature that lets you enable break and continue inside when was already described in the 1.3.60 release blog post.

Thank you!

We’re really grateful to everyone who has tried the Kotlin EAPs and experimental features and has given us feedback. We are developing the Kotlin language together with you, and making many design decisions based on your invaluable inputs. Keeping this fast and effective feedback loop going with the community is really important to help Kotlin become the best it can be!

We’re really grateful to all members of our community who are creating so many amazing things with Kotlin. Let’s continue to Kotlin together!

By the way, the Kotlin plugin inside IntelliJ IDEA and Android Studio collects anonymized statistics of your usage of its functionality. We want to kindly ask you to opt into these statistics, as they help us understand what works, what is causing difficulties, and what we should focus on improving.