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 surveyRegister for the webinar

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.

Go to the survey

Webinar with Roman Elizarov and Svetlana Isakova

The webinar will take place on July 13, 17:30 CEST. 

This is a great chance to get answers to any questions you have about the features before going through the survey. You will still be able to take the survey after the webinar.

Register for the webinar

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.

// Works because String defines companion object:
val String.Companion.CASE_INSENSITIVE_ORDER: Comparator<String>
   get() = ...

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: 

fun loadData(path: String) {
   try {
       findDataLoader(path).loadXmlData()
   } catch (e: IOException) {
       println("Failed")
   } catch (e: JDOMException) {
       println("Failed")
   }
}

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:

  
sealed class ParseResult {
   object Empty: ParseResult()
   data class Data(val data: Data, val status: Status): ParseResult()
   data class Failure(val errorDetails: ErrorDetails): ParseResult()
}

fun String.parseData(): ParseResult

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:

 
  sealed class ParseResult {
    Empty, // just like enum entry
    Data(val data: Data, val status: Status),
    Error(val errorDetails: ErrorDetails)
}
  

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:
 
fun String.parseData(): Data | Failure

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

 
fun loadData(path: String) {
   try {
       findDataLoader(path).loadXmlData()
   } catch (e: IOException | JDOMException) {
       println("Failed")
   }
}

More details: KT-7128

Kotlin properties overriding Java accessors

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

  // Java
public interface JNamed {
   String getName();
}
// Kotlin
// Doesn't work:
class KNamed(override val name: String): JNamed

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:

  // Interface.common.kt
expect interface I {
   val type: Int
}
// Interface.jvm.kt
// Doesn't work:
actual typealias I = JavaInterface

// JavaInterface.java
public interface JavaInterface {
   public int getType() {
       return 42;
   }
}

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.

  package foo

// No way to define a package-private function:
/*package-private?*/ fun bar() { ... }
 

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:

class View { // View is a dispatch receiver
   val Float.dp // Float is an extension receiver for dp property
       get() = this * resources.displayMetrics.density
      // this refers to Float
      // resources, or this@View.resources, is a property in View
}

You can only call the

dp

property within the scope where View is available as a receiver:

  with (view) {
   42f.dp
}

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:

  context(View)
val Float.dp
   get() = this * resources.displayMetrics.density

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:

context(Logger)
fun performSomeBusinessOperation(params: Params) {
  info("Operation has started")
}
context(Transaction)
fun updateUserSession() {
    val session = loadSession()
    session.lastAccess = now()
    storeSession(session)
}

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),

&amp;

(and),

&lt;&lt;

(left shift),

&gt;&gt;

(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:

out = out or (inByte and 0x7F) shl (bytes++ * 7)

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:

  out |= (inByte & 0x7F) << (bytes++ * 7)

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:

  data class Address(
   val street: String,
   val city: String
)
val (myStreet, myCity) = myAddress
  

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:

  val (street = myStreet, city = myCity) = myAddress

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:

val list = [1, 2, 3]
val map = ["one": 1, "two": 2, "three": 3]

By default, such literals would create a read-only

List&lt;Int&gt;

and

Map&lt;String, Int&gt;

, but if you need different types, you should be able to specify them explicitly or have them inferred from the context:

val set = Set<Int> [1, 2, 3]
val map: MutableMap<String, Int> = ["one": 1, "two": 2, "three": 3]

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:

val set = Set.build(3) { add(1); add(2); add(3) }
val map = Map.build(3) { put("a", 1); put("b", 2); put("c", 3) }

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 […]):

val points: List<Point> = [Point(10, 20), Point(20, 40), Point(30, 60)]
val points: List<Point> = [(10, 20), (20, 40), (30, 60)]

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:

val point: Point = (10, 20)
val pair: Pair<Int, String> = (12, "abc")

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):

val pair = (10, 20)

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:

fun parseNextInt(line: String): (value: Int, nextPosition: Int)

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

:

  MyTable.select { MyTable.ordersNumber lessEq limit }

Since

equalTo

can’t return a custom type, these comparisons are defined as

infix

functions with explicit names:

infix fun <T : Comparable<T>> Expression.lessEq(t: T): ComparisonOp
infix fun <T : Comparable<T>> Expression.less(t: T): ComparisonOp

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

&lt;=

directly:

  MyTable.select { MyTable.ordersNumber <= limit }

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

?

  private val items = mutableListOf<Item>()
   public get(): List<Item>
  

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:

val list = listOf("a")
list + "b"

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:

package mypackage
import mypackage.Color.*

// Available by short names in the whole file:
enum class Color { RED, GREEN, BLUE }
fun test(color: Color) = when (color) {
   RED -> 1
   GREEN -> TODO()
   BLUE -> TODO()
}

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:

sealed class UserResult {
    data class Found(val user: User) : UserResult()
    data class NotFound(val reason: ErrorCode) : UserResult()
}
fun processResult(result: UserResult) = when (result) {
    is UserResult.Found -> ...
    is UserResult.NotFound -> ...
}

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:

// Only available by short names inside when:
fun test(color: Color) = when (color) {
   RED -> ...
   GREEN -> ...
   BLUE -> ...
}

The same applies to

sealed

subclasses:

fun processResult(result: UserResult) = when (result) {
    is Found -> ...
    is NotFound -> ...
}

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:

// Works without imports:
val color: Color = RED
widget.color = RED
fillWithColor(RED)

This could greatly simplify repetitive code like modifier

= Modifier.foo()

when resolving this Modifier method in the context of the parameter type (with the

Modifier

expected type):

Image(
    image, 
    // Currently, you write modifier = Modifier.preferredHeightIn()
    modifier = preferredHeightIn(maxHeight = 180.dp).fillMaxWidth()
) { ... }

More details: KT-16768