Kotlin
A concise multiplatform language developed by JetBrains
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.
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:
- The official Kotlin documentation about contribution
- Kotlin evolution principles
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.
- Companion objects and static extensions
- Multicatch and union types
- Kotlin properties overriding Java accessors
- Package-private visibility
- Multiple receivers on extension functions and properties
- Default implementations for expect declarations
- Overloadable bitwise operators like I and &
- Name-based destructuring
- Collection literals
- Structure literals (Tuples)
- Operator and DSL improvements
- Lateinit for nullable and primitive types
- Having “public” and “private” property types
- Annotation to warn about the unused return value
- Unqualified enum constants & sealed subclasses in when expressions
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:
- Do not do anything conceptually new, as that would complicate the language. Instead optimize the JVM bytecode for private companion objects where possible.
- 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.
- 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:
- 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.
- 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:
- Introduce a new Java / C#-like visibility modifier that sits somewhere in between
internal
andprivate
(limited to the same package in the same module). - 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:
- 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 internalUNINITIALIZED
value instead and check it on each access (similar to how it currently works withnulls
). However, using another special value modifies the type of the property and therefore would complicate the scenarios of using nullablelateinit
properties for dependency injection frameworks (the proper type of nullable field would no longer be recognized by those frameworks, causing strange errors). - 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 forlateinit 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 = Modifier.foo()
when resolving this Modifier method in the context of the parameter type (with the Modifier
expected type):
More details: KT-16768