Kotlin Features Survey Edition #2

We’re planning the next steps for Kotlin’s development and would like to know your opinion about the relative importance of upcoming features. To this end, we’re launching the Kotlin Features Survey, along with a webinar hosted by Roman Elizarov and Svetlana Isakova. 

The survey provides an opportunity for you to have your say about the features that should be prioritized in Kotlin. You can find the descriptions of all the features and their use cases below. During the webinar you will be able to ask your questions and discuss the future of the language. 

Go to the survey

One of the goals of Kotlin, as a pragmatic language, is to stay both concise and versatile at the same time. That’s why designing new features is always hard. New features bring new functionality, but they can complicate the language and provide an easy opportunity for misuse.

The use cases of our users are our brightest beacon in this ocean of choices and trade-offs, helping us navigate and make the right language design decisions. Features that address popular use cases are a lot more likely to be implemented first.

Webinar with Roman Elizarov and Svetlana Isakova

The webinar is over. You can watch the recording.

Helpful materials

If you’re interested in learning more about the Kotlin design process and your opportunities to contribute to it, take a look at these materials:

Feature descriptions and instructions 

Below, you will find the descriptions of features and the use cases they address. To vote, please fill out the form

We ask you to choose the three features that would bring the most benefit to you, and downvote one that will create more problems for your code. We also ask you to vote for specific solutions, as some of the potential features can be implemented in multiple ways and in some aspects they may contradict one another. Your votes will help us a lot!

Please note that this is not an exhaustive list of features we are currently working on. The listed features are also at various stages of development. We cannot promise that any of these features will make it into the language any time soon, even if they receive a lot of votes. You also won’t see any of them in version 1.6, as your votes help us to prioritize our work in the longer term.

Go to the vote

Companion objects and static extensions

Problem: Suppose that we want to define the String.CASE_INSENSITIVE_ORDER property that is available on the String class directly (not on a String instance). In Kotlin, you can have an extension to the class if, and only if, the corresponding class declares a companion object.

You cannot add a similar extension to a third-party class that does not have a companion object.

Related problem: In libraries, there’s often the need to group a number of declarations together so that they can be used with a common prefix. Well-known examples include Delegates.notNull in the standard library and Dispatchers.Default in kotlinx-coroutines. The only mechanism for this that Kotlin currently provides is the object (companion or not). However, exposing an object instance is a significant maintenance commitment for a library when no instance is actually needed.

Another related problem: Inside declarations of your own classes, companion objects represent the only way to declare static members that are private to the class, which adds additional overhead to the JVM bytecode, especially when compared with the static methods in Java.

Possible solutions:

  1. Do not do anything conceptually new, as that would complicate the language. Instead optimize the JVM bytecode for private companion objects where possible.
  2. Introduce static members like in Java / C# / JavaScript with some sort of static extension syntax. This would make it possible to write static helper functions closer to where they are needed, without having to group them together. Companion objects will remain supported for cases where they need to extend a class or implement an interface.
  3. Introduce the new concept of a namespace – a kind of ephemeral object without an instance that every class automatically possesses, so that an extension to a class’s namespace can be introduced to any third-party class, and namespace members are naturally compiled down to static members on the JVM. This keeps static helpers grouped together in the source, but removes all the object overhead.

More details: KT-11968 

Multicatch and union types

Problem: When working with exception-heavy Java APIs in Kotlin, you cannot catch multiple exception types in a single catch() clause. You’ll have to write multiple catch() clauses or use a when expression to test the exception type: 

Related problem: When writing Kotlin APIs that can succeed or fail in various ways that yield additional information, you’d use a sealed class as a result, providing a type-safe listing of all possible types of errors that a function can fail with. This can become quite verbose too:

Possible solutions:

  1. Don’t do anything specifically for exceptions because modern Java APIs don’t abuse exceptions that much (and older ones will eventually be superseded), but provide a concise enum-like syntax for declaring sealed classes, so that Kotlin-style error-returning functions are easier to write.

We could have a simpler syntax for the cases that are “between” enums and full-power sealed classes, which would be equivalent to enums if all the options are constants:

That would make it possible to implement various optimizations, like efficiently implementing constants without separate classes and generating the correct toString for them right away.

  1. Add support for union types, so that it is possible to declare a function returning one of the possible values without having to introduce a dedicated ParseResult type at all:

This also provides a syntax for multicatch when working with legacy Java APIs:

More details: KT-7128

Kotlin properties overriding Java accessors

Problem: Kotlin properties can’t override Java-style getters and setters:

A Kotlin property can’t override Java’s getName() method (or the name() method in a Java record). This is because Kotlin sees a Java interface with the getFoo() method as having a member function getFoo() (which can be overridden) and an extension property foo for idiomatic access from Kotlin (which cannot be overridden).

Related problem: In multiplatform projects, Kotlin’s expect val/var cannot be provided by an actual Java function:

Supporting this feature could greatly simplify the process of adding idiomatic common Kotlin modules for Java libraries.

Possible solution:

Attempts to override Java functions with Kotlin properties could be supported (as opposed to producing an error). This could cause a number of weird cases in mixed Java/Kotlin hierarchies (e.g. when one interface overrides a Java getFoo() function with a Kotlin getFoo() function, while another interface overrides it with a Kotlin foo property). All those cases could be detected during compilation to report errors, and various JVM-specific annotations could be provided to explicitly specify what is supposed to happen in such cases and how to work around the corresponding errors. 

More details: KT-6653

Package-private visibility

Problem: In Kotlin, you can limit the scope of declarations to either a file (which is usually small) by using a private modifier or to a whole module (which tends to be big) by using an internal modifier. There’s no intermediate scope that allows a group of related files to share their implementation details, like package visibility in Java does. Packages in Kotlin are simply namespaces: they provide “full names” to their content declarations but have no visibility restrictions.

Instead, Kotlin has internal visibility, which means a declaration with this visibility modifier is “visible inside a module”. 

Still, many developers would like to have package-private visibility for their mixed Java / Kotlin projects.

Possible solutions:

  1. Introduce a new Java / C#-like visibility modifier that sits somewhere in between internal and private (limited to the same package in the same module).
  2. Don’t add any new visibility, but instead introduce a way to mark a package as “confined / isolated”, so that all internal declarations in the package become visible only to this package. 

More details: KT-29227

Multiple receivers on extension functions and properties

Problem: In Kotlin, you can define an extension function inside a class. A member extension function inside a class has two receivers – a dispatch receiver from the class and an extension receiver from the extension member:

You can only call the dp property within the scope where View is available as a receiver:

It would be useful to be able to define such a member, not only as a member function or property but also outside of the context class (View). That would make it a function or property with multiple receivers (in this example, with two receivers: View and Float). When the context class belongs to a separate library, it would be the only option.

Possible solution:

We could define a function or property to be used only in contexts where a specific receiver is available:

When you use the dp property, you would still need to provide View as an implicit receiver, as before. But now it wouldn’t need to be a member of the View class.

You could define a top-level function requiring scope. For example, you could inject a logger, or specify that an operation can be performed only in the context of the database transaction:

You wouldn’t be able to call such a top-level function as an extension on its context receiver (logger can’t become an explicit receiver). The function could only be called “in the context with this receiver”, but the receiver wouldn’t become the main object operated on, as is the case with extensions.

You could define several context receivers, as well as ones with generic parameters. Read the detailed description of this functionality’s design, including the reasoning of how to conceptually distinguish between use cases of context receivers and regular extension receivers in the corresponding “Context receivers” KEEP.

More details: KT-10468, KEEP-259

Default implementations for expect declarations

Problem: Currently, expect declarations in the common code can’t have default implementations. Functions, properties, and member functions inside an expect class can’t define bodies. It’d be useful to provide default implementations in the common code and tweak them in the platform code if needed. 

Possible solution direction:

Allow specifying default implementations for expect declarations.

More details: KT-20427

Overloadable bitwise operators like I and &

Problem. Bitwise operators like | (or), & (and), << (left shift), >> (right shift), ^(xor, “exclusive or”), and ~(tilde, or “bitwise negation”) are used mainly for bit-manipulation code.

In Kotlin, you use them by writing their explicit names:

While this is more readable for developers unfamiliar with bit manipulation practices, many people who are used to writing such code in other languages find this approach too verbose and limiting.

Possible solution:

Add the corresponding operators to make this code familiar for people who often write bit-manipulation code:

Following the general Kotlin philosophy, these operations would become overloadable operators in Kotlin, also to be used with Number types from different libraries. But it’s also important to consider that this could make it too easy to misuse them in DSLs and APIs.

More details: KT-1440

Name-based destructuring

Problem. Position-based destructuring works great for “set in stone” library classes like Pair or Triple, but it presents a danger when working with custom data classes:

If we later modify the class and add the property postalCode in the second position (between street and city), the Kotlin code will continue to compile. But it’ll contain a bug: because of the positional destructuring, the postalCode data will be assigned to the myCity variable.

In addition to this, positional destructuring does not scale to real-life entities that can have dozens of properties, of which a particular piece of code may only need to retrieve a few.

Possible solution:

That’s a known problem when using positional destructuring with custom data classes, and name-based destructuring should solve it:

You would then refer to properties by names, not by their positions. If you add a new postalCode property, the code would continue to work correctly and assign the right property to the myCity variable.

If we introduce the new named destructuring syntax, we would be able to consider making the positional destructuring of data classes an opt-in feature (with some kind of positional class modifier) and stop offering it by default for all data classes.

More details: KT-19627

Collection literals

Problem. In Kotlin, you create collections by calling library functions, such as listOf(1, 2, 3) or mapOf(“one” to 1, “two” to 2, “three” to 3). Such invocations look verbose for data-heavy applications. What’s more, the general creation of collections is currently inefficient in Kotlin due to the underlying use of varags.

Possible solution:

We could make collection creation more concise and convenient by having a specialized syntax:

By default, such literals would create a read-only List<Int> and Map<String, Int>, but if you need different types, you should be able to specify them explicitly or have them inferred from the context:

The construction of collection literals would be efficient by design. The underlying mechanism would not rely on varargs, pairs, or other wrappers.

As usual, our goal is to provide a general solution, not a built-in syntax for a specific use case. We need a solution that works for library types, as well as for user-defined types. The syntax above is supposed to be desugared into something like:

A user-defined collection type would have an option to provide a similar kind of “collection builder operator” that is recognized by the compiler and used under the hood.

More details: KT-43871

Structure literals (Tuples)

Problem: Kotlin doesn’t support tuples, finite ordered sequences of elements, which are a common feature in most modern languages. If you need to return several values from a function, you should either explicitly use the library classes Pair or Triple, or define a new data class for this case. While defining a separate class is generally a better solution from the API design perspective, it might feel too verbose.

Related problem: It would be convenient to be able to create data classes without calling constructors explicitly, especially for data-heavy scenarios, or when your data class has many optional constructor parameters. Compare the following two declarations (assuming we can use collection literals syntax […]):

The second one reads much better when you need to create lots of Points in the code.

Possible solution:

Introduce structure literals. You could explicitly specify their types:

Without explicit specifications, the types of tuples could represent anonymous data classes (or value classes, when they become supported). The component names could be either explicit or implicit, and you could omit the type, so the following variable would get an anonymous tuple type (Int, Int):

Tuple types should support naming their components, so (x = Int, y = Int) is different from (first = Int, second = Int).

Then you would be able to use tuple types for the ad-hoc returning of multiple values from a function:

However, in order to assign this anonymous tuple value to a real type, the compiler would need to perform under the hood “conversion”. That poses new challenges. For instance, you would potentially observe weird and unintuitive behavior if a value were implicitly converted and became not equal to its original value (it’s no longer possible to look it up in an ordinary collection). Allowing user-defined conversions would add an additional non-trivial thing to learn and understand, amounting to a potential “overcomplication” of the language.

More details: KT-8214, KT-45587

Operator and DSL improvements

Problem: Kotlin supports overloading for a restricted set of commonly used operators. This and other features (like extension lambdas) play a big role in improving APIs and creating DSLs. Sometimes, however, the exact operator conventions seem too restrictive, and there are different requests for extending them.

One of the most common requests is to upgrade the convention for comparison operators to allow the compareTo method return a custom type instead of Int. Currently, the replacements must be defined as functions named lt/le or less/lessEq:

Since equalTo can’t return a custom type, these comparisons are defined as infix functions with explicit names:

Possible solution:

Review the current list of conventions, trying to extend them when possible (of course, this can only be done in a backward-compatible way), so that this and similar requests could receive better support in the corresponding DSLs and frameworks. Like using <= directly:

However, for consistency, this would mean that the equality comparison operator == would also be allowed to change its meaning for certain types in certain contexts, and that it would potentially return something other than a Boolean value in those cases. 

More details: KT-45666, KT-17307

Lateinit for nullable and primitive types

Problem: In Kotlin, a lateinit property can be neither nullable nor of a primitive type, like Int or Long. This restriction is connected with the underlying implementation: the “uninitialized value” is stored as null. However, this restriction is often seen as artificial, surprising, and inconvenient. Why not simply allow lateinit to be nullable and allow lateinit primitives?

The reason is that lateinit in Kotlin/JVM serves the dual role of being used as an injection point for DI (dependency injection) frameworks and also of exposing the underlying field as a public field on the JVM for those frameworks.

Possible solutions:

  1. Lateinit primitives could be implemented by boxing, or by using an additional bitmask. For nullable types, the compiler can’t use null as an uninitialized marker, but it could use another internal UNINITIALIZED value instead and check it on each access (similar to how it currently works with nulls). However, using another special value modifies the type of the property and therefore would complicate the scenarios of using nullable lateinit properties for dependency injection frameworks (the proper type of nullable field would no longer be recognized by those frameworks, causing strange errors).
  2. Decouple lateinit into two separate features. Leave it alone for DI frameworks (with current limitations on non-nullity and non-primitiveness) with potential deprecation and replacement in the future. Focus on optimizing simple delegated properties, so that var property by Delegates.lateInit() is efficiently compiled, supports all Kotlin types, and can be used in all non-DI use-cases as a replacement for lateinit var property.

More details: KT-15284

Having “public” and “private” property types

Problem: We often store values in a MutableList property, and use this property inside the class as a MutableList, but what if we want to expose it outside the class as a read-only List?

Possible solution:

We could explicitly have public and private types for the same property!

Annotation to warn about the unused return value

Problem: Sometimes the result of a function call shouldn’t be ignored. If you forget to use this result in some way, it’s a straightforward indication of an error. One example is not assigning the result of adding an element to a list somewhere:

The initial list hasn’t changed, and the list returned by + is lost. Another “trap” is forgetting to call a terminal operation on Sequence.

One specific case where it’s a mistake to ignore the result is pure functions. A function is called pure if its result depends on the arguments only and doesn’t operate over the global state. Such a function can only use other pure functions and constants.

One more example is the result of an async-style function, which returns Deferred or another kind of “future” (“promise”) object. If you never access the Deferred value afterward and its computation ends up with an exception, that exception is going to be ignored. 

Possible solution:

Introduce an annotation on types indicating that the value can’t be ignored and should be somehow used. The exact name still needs to be discussed – for now, let’s call it CheckResult. The async and future functions from the kotlinx.coroutines package could return a value of @⁠CheckResult Deferred type, and the compiler could emit an error if your function returns a value that you never use.

Possible annotation names (you can indicate your favorite if you vote for this feature):

  • CheckResult
  • CheckReturnValue
  • NoDiscard
  • MustUseReturnValue
  • MustUse

An alternative would be to “swap the default”:

  • Start emitting the compiler warnings if the result of any function is ignored.
  • Introduce an annotation (like DiscardableResult) to mark the functions for which dropping the result is fine.

More details: KT-12719, KT-22507

Unqualified enum constants & sealed subclasses in when expressions

Problem: Referring to enum constants with fully qualified names like Color.BLUE often looks verbose. Of course, there is an alternative – importing constants by name. But then they become available in the whole file and not just inside the given context like a when expression:

What’s more, the import trick may not work with short universal constant names like ON and OFF, since it can result in ambiguous names.

Related problem: When enumerating all the subclasses of a sealed class, you also have the option of either importing the subclasses explicitly or using the qualified names:

That’s verbose, too.

Possible solution:

Support context-sensitive resolution for names. The compiler could resolve the when branch condition in the context with the expected type, specifically, with the type of the when argument:

The same applies to sealed subclasses:

The use-cases for context-sensitive resolution are not limited to enums, sealed classes, and the context of when expressions. It works whenever there’s an expected type:

This could greatly simplify repetitive code like modifier = when resolving this Modifier method in the context of the parameter type (with the Modifier expected type):

More details: KT-16768

image description