Kotlin - Going Native!
Kotlin is undoubtedly the best language for writing Android applications. It's also a great language for the JVM (and in our opinion the best too). Not to sit on their laurels, the Kotlin teams are also providing the ability to use Kotlin for JavaScript and native environments. In this post we'll explore Kotlin Native.

Kotlin/Native is a technology for compiling Kotlin code to native binaries. Applications and libraries written in Kotlin Native run without a virtual machine and this is useful in scenarios where a virtual machine is not available or desirable.
Since the output is native, the resultant binaries are NOT portable, but the source can be. We can also easily create multi-platform libraries where we have Kotlin source that is compiled to multiple platforms - JVM, Android, JS, Native MacOS, Native iOS, Native Windows etc.
Generating Native Instructions
LLVM
Kotlin Native utilises LLVM. LLVM (not an acronym….anymore 1) is a set of modular compiler tools. It’s backend compilers compile a language independent Intermediate Representation (IR) into various platform specific native binaries. Front end compilers can be written to compile any high level language into this IR. This allows many high level languages to be compiled into platform specific native instructions. For example,
Supported Platforms
Kotlin Native is still beta but already supports,
- iOS (arm32, arm64, emulator x86_64)
- MacOS (x86_64)
- Android (arm32, arm64)
- Windows (mingw x86_64)
- Linux (x86_64, arm32, MIPS, MIPS little endian, Raspberry Pi)
- WebAssembly (wasm32)
There are tools provided to translate C and Objective C (and therefore, indirectly, Swift) libraries into Kotlin libraries and then use them directly from Kotlin. This build step has an easy integration into Gradle.
On each platform, specific bindings for common libraries have already been created. For example,
platform.posix
platform.OpenGL
platform.UIKit
- etc
Building an Application
There are several moving parts in the tool chain,
kotlinc
- Kotlin Compilercinterop
- A tool for generating Kotlin Libraries (klib) from C/ObjC headers/librariesorg.jetbrains.kotlin.multiplatform
- Gradle plugin for multi-platform Kotlin projects
The easiest way to get started is to use the IDE (CLion or IntelliJ) to create a new Kotlin Native project.
// build.gradle.kts - Kotlin Gradle DSL
plugins {
kotlin("multiplatform") version "1.3.50"
}
repositories {
mavenCentral()
}
kotlin {
macosX64("native") {
binaries {
executable()
}
}
}
This configures tasks for building and testing a native Mac OS application. The macosX64
call can be replaced depending on platform,
macosX64
iosArm32
linuxX64
linuxX64
mingwX64
Importing C / Objective C
As mentioned above, many frameworks and libraries are supported out of the box.
platform.posix
platform.OpenGL
platform.UIKit
- etc
You can also pull in other dependencies via Gradle as you would in any other Kotlin project,
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core-native:1.3.2")
}
If you need to import additional C / ObjC headers you can create .def
files which describe the headers, compiler options and linker options. These are processed by the cinterop
tool to produce klibs
or Kotlin bindings that can be used to call the C code easily from Kotlin. Marshalling and translation is handled largely by the klib
.
headers=ncursesw/ncurses.h
compilerOpts = -I/usr/local/opt/ncurses/include
linkerOpts = -L/usr/local/opt/ncurses/lib \
-lncursesw
You can use platform specific compiler and linker options to have a single .def
file that supports multiple native platforms,
headers=ncursesw/ncurses.h
compilerOpts.mingw_x64 = -I../ncurses/mingw64/include
linkerOpts.mingw_x64 = -L../ncurses/mingw64/lib \
-lncursesw
compilerOpts.macos_x64 = -I/usr/local/opt/ncurses/include
linkerOpts.macos_x64 = -L/usr/local/opt/ncurses/lib \
-lncursesw
Although the cinterop
tool will do the work in the background, the easiest way to invoke it is via Gradle
kotlin {
macosX64("native") {
val ncurses by main.cinterops.creating
Writing Kotlin
Once the build is set up, the code you write will largely be standard Kotlin. Since it is not running on the JVM, a large swathe of the libraries you know and love will not be present - since they are really Java libraries.
But you will have access to the standard platform libraries, any native or multi-platform libraries that you import and any C / ObjC klib
s that you import / generate.
import platform.posix.*
fun main() {
ncurses.initscr() // Initialise Screen
ncurses.cbreak() // Read user input without buffering
val log = fopen(filename, "r")
// ...
}
Memory management is still handled for you if you within your Kotlin code but the implementation is very different. At runtime it uses reference counting (with a garbage collector for cycles). So the great thing is, when writing Kotlin for Native, not much changes.
When crossing the boundary between Kotlin and other languages/platforms, there are more considerations. Native specific concerns include,
- Native Memory Management - allocating and freeing bytes!
- Marshalling Data - moving data back and forth between Kotlin and native code - e.g reference counting implementation is compatible with Swift / ObjC but not C
- Threading - Kotlin Native has tight constraints on what can be shared between threads
- Passing Functions - calling Kotlin from native and calling native from Kotlin
- Supported Language Features - e.g. Suspend functions are not supported between Kotlin and C / ObjC
There’s too much here to get into within this post and some of this is a moving target as it is still beta. For example,
- The threading model may evolve to support common patterns
- Coroutines are largely limited to the main thread at the time of writing so a solution needs to be developed
- Some standard cross platform concerns may be provided by kotlin libraries
- Kotlin Native or multi-platform libraries and frameworks are popping up all the time
Multi-platform
With all of the different flavours of Kotlin it can be daunting planning how to manage the code. Some code will be compatible on all platforms (JVM, Android, JS and various native platforms) and some will be specific to a single platform. This is where multi-platform projects come in.
With a multi-platform project we can have a single project that includes the common and specific code. It can be used to build different artefacts e.g. JVM .jar
file, native .dll
or .dylib
file etc.
Within the common area we can define placeholders for entities (classes, functions, globals etc) with the expect
keyword.
expect fun readImage(name: String, width: Int, height: Int): Image?
This can then be implemented specifically for each platform using the actual
keyword,
actual fun readImage(name: String, width: Int, height: Int): Image {
// Platform specific implementation
}
These projects maximise the reuse of Kotlin across multiple platforms.
Conclusion
Although beta, Kotlin Native is a very interesting project and already has lots of functionality and potential. One of the most compelling stories is using Kotlin to write common code for a mobile application in a multi-platform project. This can output a native iOS framework or an Android library. A thin native iOS (Swift) and Android (more Kotlin :-) ) layer can then be added on top, thus reducing overhead and cost in producing multi-platform mobile applications.
The same pattern can be used for sharing models between the frontend and backend. You just need to reach the critical mass of shareable logic.
At Instil, we are keeping a close eye on this one.
-
LLVM originally stood for Low Level Virtual Machine but this acronym has been dropped to avoid confusion with actual Virtual Machines. ↩