Hello! My name is Anton Arhipov and in this
video, I will highlight the new features in Kotlin 1.5.30. First of all, I start with the disclaimer.
Pretty much everything I'm talking about in this video is experimental. You either need to
enable the features using a compiler option or you need to opt-in for the usage of the
experimental API. Either way the features are in the preview state and are expected to stabilize
by Kotlin 1.6. I know you are watching this video because you like Kotlin very much. But I also know
that every second person who watches this is not subscribed to our awesome YouTube channel. So hit
that subscribe button! It's a great indication for us that we are doing a good job. There are
a few changes to the language. Specifically. there are changes in how opt-in requirements work.
There are updates in the area of type inference; we are adding the exhaustive behavior to the
when statement, and there is now a possibility to use the suspend functions as supertype. For
the JVM target now it is possible to instantiate annotation classes, and there are improvements
in handling the nullability annotations. We also added a number of experimental functions
in the standard library. Specifically, there are updates to the experimental Duration API, and the
new functions were added to the Regex API as well. There are a few interesting updates in
Kotlin/Native. We added support for Apple silicon, and now we are working on improving
the interoperability with Swift and Objective-C. Kotlin/JS IR backend is promoted to Beta, and
it leads to the related tooling improvements. Let's talk about language-related changes.
But before we look into what's new, I think it's important to recall what is it about.
Opt-in requirements is a tool for acquiring and giving an explicit consent for using certain
elements of API. The library author can mark an experimental API as requiring opt-in to inform the
users about the experimental state. The compiler then raises a warning or an error on the API usage
and requires an explicit consent to suppress it. In this example, you can see that the DateProvider
class is marked with MyDateTime annotation that is actually a requirement annotation. The compiler
will raise a compilation error in this case. I have to explicitly consent to the
use of DateProvider class by using an OptIn annotation where I say that I'm
OK with using the experimental class that is marked with MyDateTime annotation. I
could also use MyDateTime annotation directly to mark the function instead of using the
OptIn annotation. However, it means that I'm declaring my function as experimental and I
would have to opt-in for the use of this function in some other location in my code. We may say
that MyDateTime annotation is a requirement, and OptIn annotation is a consent to a specific
requirement. So if we consent to the usage of this class that is marked as requiring opt-in, we don't
really propagate the requirements further. But there are still situations where we may still use
this experimental class implicitly. For instance, when you are using a function that either
accepts the experimental class as an argument or specifies it as a return type. The compiler wasn't
detecting the implicit usage of experimental API and didn't raise the warning. Starting from
1.5.30, the compiler detects such situations and requires opt-in even for the implicit usages of
the experimental API. In this example, we can see that the return type is marked as an experimental
API element hence the usage of the function requires you to opt-in even if the declaration
is not marked as requiring an opt-in explicitly. As part of the stabilization
story for opt-in requirements, Kotlin 1.5.30 presents the new rules for using
and declaring opt-in requirements annotations on different targets. We are now limiting the number
of cases where these requirement annotations could be used. So you wouldn't apply it in places where
these are not meant to be used. For instance, marking local variables with opt-in requirement
annotations is now forbidden at the use site. Also, marking an override is allowed only if
the corresponding declaration in the super type is also marked with the requirement annotation.
Of course, you can find the full list of the restrictions for the usages of opt-in
requirement annotations in the documentation. Let's now talk about type inference. Oh, this
one is interesting! In Kotlin or in Java you can define a recursive generic type which
references itself in its type parameters. Since Kotlin 1.5.30 the compiler can infer a
type argument based only on the upper bounds of the corresponding type parameter if it's
a recursive generic. This enables various patterns with recursive generic types that
are often used in Java to make a builder API. Let's take a look at the example. I'm using
TestContainers Java library and they use recursive generics quite extensively.
Previously, to use this library, I had to use Nothing as a type parameter for
instantiating the types provided by the library. This is because generic type information could
not be inferred. And there is no better option this time than specifying Nothing as a parameter.
And then I had to use the apply function from Kotlin standard library to call the sequence of
builder functions for that type. In Kotlin 1.5.30 we can now skip specifying the type parameter.
The compiler is able to infer the types for us and we can use the builder API as designed by the
library authors. This is a great improvement for Kotlin's interoperability with Java! However,
you remember that everything I talk about in this video is experimental, right? So
this feature is not enabled by default yet, and you can switch it on by passing the
-Xself-upper-bound-inference compiler option. Next on the list is builder inference. This is
a special kind of type inference which allows to infer type arguments of a call based on the
type information from other calls inside its lambda argument. This can be useful when
calling generic builder functions like buildList() or sequence(). However, inside such
lambda argument there is currently a limitation on using the type information that builder
inference tries to infer. You can only specify it but you cannot "get" it. For example, you
cannot call get() function inside the lambda argument of a buildList() function without
explicitly specifying the type arguments. Now we remove these limitations with the
-Xunrestricted-builder-inference compiler option. It is possible to skip specifying
the type parameter for the builder and the compiler is able to infer the
type information for us. In our example, it is now possible to call get() function inside
the parameter block for the buildList function. The 'when' statements for sealed classes and
boolean types are going to be exhaustive by default. 'when' expression is a very useful
feature in Kotlin because it's exhaustive by default when used with sealed classes, enums, and
boolean types. The exhaustive 'when' expression contains branches for all the types of a subject
or some of the types plus an 'else' branch. If not all the options are covered the result is a
compilation error. However, it is possible to use when as a statement as opposed to expression.
In this case, we don't return any value and the same situation results just in a weak warning
displayed in the IDE. The 'when' statement is not exhaustive by default. Starting from Kotlin
1.5.30, if the 'when' statement is not exhaustive, the compiler will warn you about it. There are
even plans to make it a compiler error in the future versions of Kotlin. To see this behavior
you need to set the language version to 1.6 In Kotlin, it is possible to use functional
type as a super type. It is sometimes useful when you are required to use a function but
you need to customize it somehow. For instance, you need to pass an extra parameter. But the same
didn't work if the functional type was marked with suspend keyword. This is a little bit unfortunate
because there are situations where you would want to derive from such type. In 1.5.30, it is
now possible to use suspend functional type as a super type, and of course, you have to
enable this feature via the compiler option. Now let's take a look at the
updates for the Kotlin/JVM target. Null-safety is one of those Kotlin features that
people like the most. The Kotlin compiler can read various types of nullability annotations
to get nullability information from Java. This information allows it to report nullability
mismatches in Kotlin when calling Java code. Since 1.5.30 you can specify whether the
compiler reports a nullability mismatch based on the information from the specific type of
nullability annotations. To make it work, use the compiler option, -Xnullability-annotations, where
you specify the package name and reporting level. You can find a full list of packages in our
documentation for nullability annotations. And the report level is one of the following:
'ignore' to ignore nullability mismatches, 'warn' to report warnings, 'strict' to
report errors. And here is an example of how you can enable error reporting for newly
supported RxJava 3 nullability annotations. In Java, annotations are presented
as interfaces, and therefore it is possible to implement those interfaces. We
can see that if we decompile the class file of a Java annotation. Various Java frameworks
and libraries make use of this and require an instance of annotation interface for
operations. For instance, a framework may use annotations to create in-memory configuration
for the application. Interoperability with Java frameworks and libraries is a priority for
us. With Kotlin 1.5.30, you can now call constructors of annotation classes in
arbitrary code to obtain the resulting instance. The feature covers the same use cases
as implementing the annotation interface in Java. To enable this feature you need to set the
language version to 1.6 as a compiler parameter. Kotlin 1.5.30 is introducing the native
support for Apple silicon. Previously, the Kotlin/Native compiler and tooling required
the Rosetta translation environment for working with Apple silicon hosts. Since 1.5.30, it is
not needed anymore. We are also introducing the new targets that make Kotlin code run on Apple
silicon natively. These are available on both Intel-based and Apple silicon hosts.
Note that in 1.5.30 we provide only basic support for Apple silicon targets in
the Kotlin multi-platform Gradle plugin. We have added the support for Kotlin's suspending
functions from Objective-C and Swift in Kotlin 1.4. Now we are improving it to keep up with the
new feature in Swift 5.5 - the concurrency with async and await modifiers. The Kotlin/Native
compiler now emits the _Nullable_result attribute in the generated Objective-C headers for
suspending functions with nullable return types. This makes them available for calling from Swift
as async functions with the proper nullability. Please note that this feature is
experimental and can be affected in the future by changes in both Kotlin and
Swift. For now, we are offering a preview that has certain limitations and we
are eager to hear how it works for you. Now it is possible to refer at the objects
and companion objects in a more authentic way for native iOS developers. For example,
if you have in Kotlin the following objects, to access the object in Swift you can
use 'shared' or 'companion' properties. And finally, IR based compiler back-end
for Kotlin/JS has reached Beta! Previously, we published the
migration guide for the JS IR backend to help migrate your projects to the new backend.
Now we also present the Kotlin/JS inspection pack IDE plugin that shows the
required changes directly in the IDE The Kotlin/JS inspection pack plugin is available
in the plugins marketplace and is just one click to install! This plugin adds useful inspections,
intentions, quick fixes for working with Kotlin/JS. It also provides helpful functionality
when you are building new applications using Kotlin/JS and it helps you to migrate existing
applications to the new Kotlin/JS IR compiler Kotlin 1.5.30 brings JavaScript source
maps generation for Kotlin/JS improving the debugging experience. This enables debugging
support including breakpoints, stepping, and even readable stack traces
with proper source references Now let's see what's new in the standard
library. Before this release, Duration.toString() function was supposed to return a string
representation of its argument duration value expressed in the unit that yields the
most compact and readable number value. Oh well, the intention was a good one but you
can obviously see that it didn't really work out. So now we fixed it! Now it returns a string
representation of its duration value expressed as a combination of numeric components, each
of its own unit. Each component is a number followed by the unit abbreviation name. A
negative duration is prefixed with minus sign and if it consists of multiple components
it is surrounded with parenthesis. If you want to express duration as a single unit
you can use the overloaded version of toString function which allows specifying the duration
unit and the number of decimals after the dot. We added the new Duration.toIsoString()
function that we recommend using in cases of serialization or when you need
to interchange the data somehow. This function uses a more strict ISO-8601
format instead of duration to string. We also added the new functions for parsing
the duration from a string representation. You can see the examples of parse() and
parseOrNull() usages. As you can see, the parse() function is a universal one.
It accepts the duration in any format if the duration is formatted incorrectly then
parse() function fails and throws an exception. Alternatively, you can use parseOrNull()
function that returns null in case of incorrectly formatted input. Besides
parse() and parseOrNull() functions, we also added the functions for working
with ISO-formatted durations. In this case, the functions assume that the input is correctly
formatted according to ISO specification. There are new experimental functions
in the Regex standard library class, matchAt() and matchesAt(). These functions
provide a way to check if a regular expression has an exact match at a particular position in
a string or a ChartSequence. Here is an example usage of both functions. The functions
try matching a given pattern at a specific position in a string. The difference is that
matchesAt() function returns a boolean result, and matchAt() function returns a substring that it
matches or a null value if it can't find the match The new function splitToSequence()
is a lazy counterpart of split() function. It splits the string around
matches of the given regular expression but returns the result as a sequence. So that all
the operations to the result are executed lazily. So this is the list of new things in
Kotlin 1.5.30. As I mentioned earlier, pretty much everything in this list is still
experimental and we are eager to collect your feedback. So give this new version a try and let
us know what works for you, what doesn't work, what you like, what you don't like. Thanks
for your time and have a nice Kotlin!