diff --git a/docs/topics/multiplatform/multiplatform-expect-actual.md b/docs/topics/multiplatform/multiplatform-expect-actual.md index a7a8c458d6d..33a12b87658 100644 --- a/docs/topics/multiplatform/multiplatform-expect-actual.md +++ b/docs/topics/multiplatform/multiplatform-expect-actual.md @@ -1,16 +1,16 @@ [//]: # (title: Expected and actual declarations) > The mechanism of expected and actual declarations is in [Beta](components-stability.md). -> It is almost stable, but migration steps may be required in the future. -> We'll do our best to minimize any changes you will have to make. +> It's almost stable, but migration steps may be required in the future. +> We'll do our best to minimize any further changes for you to make. > {type="warning"} -Expected and actual declarations allow you to access platform-specific APIs from the Kotlin Multiplatform modules. -Then, in the common code, you can provide platform-agnostic APIs. +Expected and actual declarations allow you to access platform-specific APIs from Kotlin Multiplatform modules. +You can provide platform-agnostic APIs in the common code. > This article describes the language mechanism of expected and actual declarations. For general recommendations on -> different ways to use platform specifics, see [Use platform-specific APIs](https://www.jetbrains.com/help/kotlin-multiplatform-dev/multiplatform-connect-to-apis.html). +> different ways to use platform-specific APIs, see [Use platform-specific APIs](https://www.jetbrains.com/help/kotlin-multiplatform-dev/multiplatform-connect-to-apis.html). > {type="tip"} @@ -20,13 +20,13 @@ To define expected and actual declarations, follow these rules: 1. In the common source set, declare a standard Kotlin construct. This can be a function, property, class, interface, enumeration, or annotation. -2. Mark this construct with the `expect` keyword. It's your _expected declaration_. These declarations can be used in - common code but shouldn't include any implementation. Instead, the platform-specific code provides this implementation. +2. Mark this construct with the `expect` keyword. This is your _expected declaration_. These declarations can be used in the + common code, but shouldn't include any implementation. Instead, the platform-specific code provides this implementation. 3. In each platform-specific source set, declare the same construct in the same package and mark it with the `actual` - keyword. It's your _actual declaration_. Typically, it contains an implementation using platform-specific libraries. + keyword. This is your _actual declaration_, which typically contains an implementation using platform-specific libraries. During compilation for a specific target, the compiler tries to match each _actual_ declaration it finds with the -corresponding _expected_ declaration in common code. The compiler ensures that: +corresponding _expected_ declaration in the common code. The compiler ensures that: * Every expected declaration in the common source set has a matching actual declaration in every platform-specific source set. @@ -34,22 +34,22 @@ corresponding _expected_ declaration in common code. The compiler ensures that: * Every actual declaration shares the same package as the corresponding expected declaration, such as `org.mygroup.myapp.MyType`. While generating the resulting code for different platforms, the Kotlin compiler merges the expected and actual -declarations corresponding to each other. It generates one declaration with its actual implementation for each platform. -Therefore, every use of the expected declaration in the common code calls the correct actual declaration in the +declarations that correspond to each other. It generates one declaration with its actual implementation for each platform. +Every use of the expected declaration in the common code calls the correct actual declaration in the resulting platform code. You can declare actual declarations when you use intermediate source sets shared between different target platforms. -Consider, for example, the `iosMain` an intermediate source set shared between the `iosX64Main`, `iosArm64Main`, -and `iosSimulatorArm64Main`platform source sets. Only `iosMain` typically contains the actual declarations, not the +Consider, for example, `iosMain` as an intermediate source set shared between the `iosX64Main`, `iosArm64Main`, +and `iosSimulatorArm64Main`platform source sets. Only `iosMain` typically contains the actual declarations and not the platform source sets. The Kotlin compiler will then use these actual declarations to produce the resulting code for the corresponding platforms. -The IDE assists with common issues, like: +The IDE assists with common issues, including: -* Missing declarations -* The expected declaration contains an implementation -* The signatures of declarations don't match -* Declarations are in different packages +* Missing declarations +* Expected declarations that contain implementations +* Mismatched declaration signatures +* Declarations in different packages You can also use the IDE to navigate from expected to actual declarations. Select the gutter icon to view actual declarations or use [shortcuts](https://www.jetbrains.com/help/idea/navigating-through-the-source-code.html#go_to_implementation). @@ -58,10 +58,10 @@ declarations or use [shortcuts](https://www.jetbrains.com/help/idea/navigating-t ## Different approaches for using expected and actual declarations -Let's explore the different options of using the expected/actual mechanism to solve the problem of accessing +Let's explore the different options of using the expect/actual mechanism to solve the problem of accessing platform APIs while still providing a way to work with them in the common code. -Consider a Kotlin Multiplatform project where you need to implement the `Identity` type, which should contain the user +Consider a Kotlin Multiplatform project where you need to implement the `Identity` type, which should contain the user's login name and the current process ID. The project has the `commonMain`, `jvmMain`, and `nativeMain` source sets to make the application work on the JVM and in native environments like iOS. @@ -72,98 +72,98 @@ and implemented differently in platform source sets: 1. In `commonMain`, declare a simple type and expect a factory function: - ```kotlin - package identity + ```kotlin + package identity - class Identity(val userName: String, val processID: Long) + class Identity(val userName: String, val processID: Long) - expect fun buildIdentity(): Identity - ``` + expect fun buildIdentity(): Identity + ``` 2. In the `jvmMain` source set, implement a solution using standard Java libraries: - ```kotlin - package identity + ```kotlin + package identity - import java.lang.System - import java.lang.ProcessHandle + import java.lang.System + import java.lang.ProcessHandle - actual fun buildIdentity() = Identity( - System.getProperty("user.name") ?: "None", - ProcessHandle.current().pid() - ) - ``` + actual fun buildIdentity() = Identity( + System.getProperty("user.name") ?: "None", + ProcessHandle.current().pid() + ) + ``` 3. In the `nativeMain` source set, implement a solution with [POSIX](https://en.wikipedia.org/wiki/POSIX) using native dependencies: - ```kotlin - package identity + ```kotlin + package identity - import kotlinx.cinterop.toKString - import platform.posix.getlogin - import platform.posix.getpid + import kotlinx.cinterop.toKString + import platform.posix.getlogin + import platform.posix.getpid - actual fun buildIdentity() = Identity( - getlogin()?.toKString() ?: "None", - getpid().toLong() - ) - ``` + actual fun buildIdentity() = Identity( + getlogin()?.toKString() ?: "None", + getpid().toLong() + ) + ``` Here, platform functions return platform-specific `Identity` instances. -> Starting with Kotlin version 1.9.0, using `getlogin()` and `getpid()` functions requires the `@OptIn` annotation. +> Starting with Kotlin 1.9.0, using `getlogin()` and `getpid()` functions requires the `@OptIn` annotation. > {type="note"} ### Interfaces with expected and actual functions -If the factory function becomes too big, consider using a common `Identity` interface and implementing it differently on +If the factory function becomes too large, consider using a common `Identity` interface and implementing it differently on different platforms. -A factory function `buildIdentity()` should return `Identity`, but this time, it's an object implementing the +A `buildIdentity()` factory function should return `Identity`, but this time, it's an object implementing the common interface: 1. In `commonMain`, define the `Identity` interface and the `buildIdentity()` factory function: - ```kotlin - // In the commonMain source set: - expect fun buildIdentity(): Identity - - interface Identity { - val userName: String - val processID: Long - } - ``` + ```kotlin + // In the commonMain source set: + expect fun buildIdentity(): Identity + + interface Identity { + val userName: String + val processID: Long + } + ``` 2. Create platform-specific implementations of the interface without additional use of expected and actual declarations: - ```kotlin - // In the jvmMain source set: - actual fun buildIdentity(): Identity = JVMIdentity() + ```kotlin + // In the jvmMain source set: + actual fun buildIdentity(): Identity = JVMIdentity() - class JVMIdentity( - override val userName: String = System.getProperty("user.name") ?: "none", - override val processID: Long = ProcessHandle.current().pid() - ) : Identity - ``` + class JVMIdentity( + override val userName: String = System.getProperty("user.name") ?: "none", + override val processID: Long = ProcessHandle.current().pid() + ) : Identity + ``` - ```kotlin - // In the nativeMain source set: - actual fun buildIdentity(): Identity = NativeIdentity() + ```kotlin + // In the nativeMain source set: + actual fun buildIdentity(): Identity = NativeIdentity() - class NativeIdentity( - override val userName: String = getlogin()?.toKString() ?: "None", - override val processID: Long = getpid().toLong() - ) : Identity - ``` + class NativeIdentity( + override val userName: String = getlogin()?.toKString() ?: "None", + override val processID: Long = getpid().toLong() + ) : Identity + ``` -These platform functions return platform-specific `Identity` instances, which are implemented as platform types `JVMIdentity` -and `NativeIdentity`. +These platform functions return platform-specific `Identity` instances, which are implemented as `JVMIdentity` +and `NativeIdentity` platform types. #### Expected and actual properties -You can modify the previous example a bit and expect a `val` property to store an `Identity`. +You can modify the previous example and expect a `val` property to store an `Identity`. Mark this property as `expect val` and then actualize it in the platform source sets: @@ -199,7 +199,7 @@ class NativeIdentity( #### Expected and actual objects -When `IdentityBuilder` is expected to be a singleton on each platform, you can define it as an expected object and let +When `IdentityBuilder` is expected to be a singleton on each platform, you can define it as an expected object and let the platforms actualize it: ```kotlin @@ -241,17 +241,17 @@ framework allows injecting dependencies into components based on the current env For example, you might inject different dependencies in testing and in production or when deploying to the cloud compared to hosting locally. As long as a dependency is expressed through an interface, any number of different -implementations can be injected, either at compile time or runtime. +implementations can be injected, either at compile time or at runtime. -The same principle applies when the dependencies are platform-specific. In common code, a component can express its +The same principle applies when the dependencies are platform-specific. In the common code, a component can express its dependencies using regular [Kotlin interfaces](interfaces.md). The DI framework can then be configured to inject a -platform-specific implementation, for example, from a JVM or an iOS module. +platform-specific implementation, for example, from the JVM or an iOS module. -This means that the only case when expected and actual declarations are needed is in the configuration of the DI +This means that expected and actual declarations are only needed in the configuration of the DI framework. See [Use platform-specific APIs](https://www.jetbrains.com/help/kotlin-multiplatform-dev/multiplatform-connect-to-apis.html#dependency-injection-framework) for examples. With this approach, you can adopt Kotlin Multiplatform simply by using interfaces and factory functions. If you already -use the DI framework to manage dependencies in your project, we recommend that you use the same approach for managing +use the DI framework to manage dependencies in your project, we recommend using the same approach for managing platform dependencies. ### Expected and actual classes @@ -282,7 +282,7 @@ actual class Identity { } ``` -You might have seen this approach in demonstration materials before. However, using classes in simple cases where +You might have already seen this approach in demonstration materials. However, using classes in simple cases where interfaces would be sufficient is _not recommended_. With interfaces, you don't limit your design to one implementation per target platform. Also, it's much easier to @@ -307,38 +307,38 @@ and reuse its functionality: 1. To solve this problem, declare a class in `commonMain` using the `expect` keyword: - ```kotlin - expect class CommonIdentity() { - val userName: String - val processID: Long - } - ``` + ```kotlin + expect class CommonIdentity() { + val userName: String + val processID: Long + } + ``` 2. In `nativeMain`, provide an actual declaration that implements the functionality: - ```kotlin - actual class CommonIdentity { - actual val userName = getlogin()?.toKString() ?: "None" - actual val processID = getpid().toLong() - } - ``` + ```kotlin + actual class CommonIdentity { + actual val userName = getlogin()?.toKString() ?: "None" + actual val processID = getpid().toLong() + } + ``` 3. In `jvmMain`, provide an actual declaration that inherits from the platform-specific base class: - ```kotlin - actual class CommonIdentity : Identity() { - actual val userName = login - actual val processID = pid - } - ``` + ```kotlin + actual class CommonIdentity : Identity() { + actual val userName = login + actual val processID = pid + } + ``` Here, the `CommonIdentity` type is compatible with your own design while taking advantage of the existing type on the JVM. #### Application in frameworks -If you're a framework author, you can also find expected and actual declarations useful for your framework. +As a framework author, you can also find expected and actual declarations useful for your framework. -Imagine that the example above is a part of a framework. The user has to derive a type from `CommonIdentity` to provide +If the example above is part of a framework, the user has to derive a type from `CommonIdentity` to provide a display name. In this case, the expected declaration is abstract and declares an abstract method: @@ -352,7 +352,7 @@ expect abstract class CommonIdentity() { } ``` -Similarly, actual implementations are abstract as well and declare the `displayName` method: +Similarly, actual implementations are abstract and declare the `displayName` method: ```kotlin // In nativeMain of the framework codebase: @@ -372,7 +372,7 @@ actual abstract class CommonIdentity : Identity() { } ``` -The framework users then need to write common code that inherits from the expected declaration and implement the missing +The framework users need to write common code that inherits from the expected declaration and implement the missing method themselves: ```kotlin @@ -382,8 +382,8 @@ class MyCommonIdentity : CommonIdentity() { } ``` - @@ -427,21 +427,21 @@ actual typealias MyDate = java.time.LocalDate In this case, define the `typealias` declaration in the same package as the expected declaration and create the referred class elsewhere. -> Since the `LocalDate` type uses the `Month` enum, you need to declare both of them as expected classes in common code. +> Since the `LocalDate` type uses the `Month` enum, you need to declare both of them as expected classes in the common code. > {type="note"} -### Widened visibility in actual declarations +### Expanded visibility in actual declarations You can make actual implementations more visible than the corresponding expected declaration. This is useful if you don't want to expose your API as public for common clients. Currently, the Kotlin compiler issues an error in the case of visibility changes. You can suppress this error by -applying `@Suppress("ACTUAL_WITHOUT_EXPECT")` to the actual type alias declaration. This limitation will be lifted -starting with Kotlin 2.0. +applying `@Suppress("ACTUAL_WITHOUT_EXPECT")` to the actual type alias declaration. Starting with Kotlin 2.0, +this limitation will not apply. For example, if you declare the following expected declaration in the common source set: @@ -451,7 +451,7 @@ internal expect class Messenger { } ``` -Then, you can use the following actual implementation in a platform-specific source set as well: +You can use the following actual implementation in a platform-specific source set as well: ```kotlin @Suppress("ACTUAL_WITHOUT_EXPECT") @@ -463,10 +463,10 @@ Here, an internal expected class has an actual implementation with an existing p ### Additional enumeration entries on actualization When an enumeration is declared with `expect` in the common source set, each platform module should have a -corresponding `actual` declaration. These declarations must contain the same enum constants, but they can have -additional constants too. +corresponding `actual` declaration. These declarations must contain the same enum constants, but they can also have +additional constants. -This is very useful when you actualize an expected enum with an existing platform enum. For example, consider the +This is useful when you actualize an expected enum with an existing platform enum. For example, consider the following enumeration in the common source set: ```kotlin @@ -486,10 +486,10 @@ actual enum class Department { IT, HR, Sales, Legal } actual enum class Department { IT, HR, Sales, Marketing } ``` -However, in this case, these extra constants in platform source sets won't match with those in common code. +However, in this case, these extra constants in the platform source sets won't match with those in the common code. Therefore, the compiler requires you to handle all additional cases. -So, the function that implements the `when` construction on `Department` requires an `else` clause: +The function that implements the `when` construction on `Department` requires an `else` clause: ```kotlin // An else clause is required: @@ -503,7 +503,7 @@ fun matchOnDepartment(dept: Department) { } ``` - + ### Expected annotation classes @@ -519,7 +519,7 @@ expect annotation class XmlSerializable() class Person(val name: String, val age: Int) ``` -This might be helpful to reuse existing types on a particular platform. For example, on the JVM, you can define your +It might be helpful to reuse existing types on a particular platform. For example, on the JVM, you can define your annotation using the existing type from the [JAXB specification](https://javaee.github.io/jaxb-v2/): ```kotlin @@ -532,7 +532,7 @@ There is an additional consideration when using `expect` with annotation classes metadata to code and do not appear as types in signatures. It's not essential for an expected annotation to have an actual class on a platform where it's never required. -Because of this, you only need to provide an `actual` declaration on platforms where the annotation is used. This +You only need to provide an `actual` declaration on platforms where the annotation is used. This behavior isn't enabled by default, and it requires the type to be marked with [`OptionalExpectation`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-optional-expectation/). Take the `@XmlSerializable` annotation declared above and add `OptionalExpectation`: @@ -550,4 +550,4 @@ error. ## What's next? -For general recommendations on different ways to use platform specifics, see [Use platform-specific APIs](https://www.jetbrains.com/help/kotlin-multiplatform-dev/multiplatform-connect-to-apis.html). \ No newline at end of file +For general recommendations on different ways to use platform-specific APIs, see [Use platform-specific APIs](https://www.jetbrains.com/help/kotlin-multiplatform-dev/multiplatform-connect-to-apis.html). \ No newline at end of file