App Development

A look into Kotlin native

black and white picture of a phoropter - an optometry tool

JetBrains recently released an early stage preview of Kotlin native (interestingly referred to as Konan). If you aren’t already familiar, Kotlin is a statically typed programming language which targets the JVM, Android, and the browser. Kotlin native gives the Kotlin compiler the ability to compile to output standalone native executables that can be run without using a virtual machine (VM). It currently targets MacOS, Linux, (iOS by cross compiling on a mac) and the Raspberry Pi. This allows you to write applications without the overhead that typically comes with a VM.

I’ve been looking forward to this since it was rumored earlier this year because of it’s potential to be a native mobile cross platform language that isn’t C/C++. After jumping head first into the new toolchain, I thought I’d share my thoughts.

Setting up a project

Setting up a Kotlin native project is fairly simple with Gradle thanks to the Kotlin native gradle plugin. Here’s what a build script looks like

group 'com.nishtahir'
version '1.0-SNAPSHOT'

buildscript {
    repositories {
        mavenCentral()
        maven {
            url  "https://dl.bintray.com/jetbrains/kotlin-native-dependencies"
        }
    }

    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-native-gradle-plugin:0.1"
    }
}

apply plugin: 'konan'

konanInterop {
    stdio {
        defFile 'stdio.def'
        includeDirs 'src/include'
    }
}

konanArtifacts {
    ktnative {
        inputFiles fileTree('src/main/')
        linerOpts '-ltest -Lsrc/lib/'
        useInterop 'stdio'
    }
}

During its first run it downloads a copy of clang-llvm for your platform as well as other dependencies. Interestingly, It also includes GCC on linux which it uses for the linker and some libs. However given that these dependencies are quite large it’s great that they are cached in your home folder so they can be used across different projects.

The Kotlin native tooling comes with the ability to generate bindings based off of C headers and produce a type mappings for functions, structs etc… as well as Kotlin method stubs that invoked in your Kotlin code. You declare headers that you want to include in your project by creating interop definitions in a *.def file which is a simple key value type properties file. In there you can also include compiler options and stuff to exclude.

headers = stdio.h stdlib.h dirent.h
compilerOpts = -D_POSIX_SOURCE
excludedFunctions =

You then specify what interops you would like to include in your project in the buildscript. You can name the closure declaring the interop whatever you want as well as specify additional include directories using includeDirs.

To use the generated interop stubs in your code, all you need to do is import them in your Kotlin code using the interop name you declared in your buildscript.

import stdio.*

You also need to tell the plugin exactly what to build using a konanArtifacts closure. Here my artifact is called ktnative and my input files are everything in my src folder. I’m also using the stdio interop I declared above.

konanArtifacts {
    ktnative {
        inputFiles fileTree('src/main/')
        useInterop 'stdio'
    }
}

Linking with statically compiled binaries also works as expected using the linerOpts property. You may choose to archive your libs using ar or statically link directly with object files.

konanArtifacts {
    ktnative {
        inputFiles fileTree('src/main/')
        linerOpts '-ltest -Lsrc/lib/' 
        useInterop 'stdio'
    }
}

Here i’m linking against a statically compiled library libTest.a which I’ve placed in the src/lib

Quirks

There are a quite a few quirks around Kotlin native right now which is to be expected from a preview release. For example, the main function can’t be in a package, autocompletion for generated interop function stubs didn’t work for me. There is virtually no tooling besides the Gradle plugin at this time of which the tasks are limited to just build and clean (the buildscripts can be quite flexible). The Intellij Kotlin plugin hasn’t caught up yet so there isn’t a way to run your program inside of Intellij. Binaries targeting the raspberrypi don’t seem work on the Raspberry Pi 1, but do work on the Pi 3.

There is still no concept of unsigned integer variables in the Kotlin language itself at the moment.

This comes despite the fact that native types are mapped to their Kotlin counterparts.

/* generated type aliases */
typealias __uint8_tVar = ByteVarOf<__uint8_t>
typealias __uint8_t = Byte

var num: __uint8_t = 255 // fails to compile

This also means that all of the type aliases declared in C/C++ code end up as generated type aliases in your code which you should be able to safely ignore but could potentially clutter up your auto complete.

Interop with C still shares some of the downsides with Kotlin JVM interop with Java such as the inability to use named arguments (in most cases I’ve seen) and the lack of knowledge about the nullability of anything you pass into or get back from functions.

fun fgets(arg0: CValuesRef<ByteVar>?, arg1: Int, arg2: CValuesRef<FILE>?): CPointer<ByteVar>? { … }

However since this is a preview it’s expected to get better over time.

Memory management

There are currently several different ways to allocate memory. The easiest Kotlin native way is to use the NativePlacement api along with a memScoped block.

val fileSize = memScoped {
    val value = alloc<ByteVar>()

    ...

    return result
}

This is pretty interesting because memory allocations are scoped. So any allocated memory will be eligible for garbage collection (with its reference counting garbage collector) once you leave the scope. Interestingly nearly all of the function stubs that are generated all wrap the native function calls inside of memScoped blocks.

fun fopen(__filename: String?, __mode: String?): CPointer<FILE>? {
    return memScoped {
        val ___filename = __filename?.cstr?.getPointer(memScope).rawValue
        val ___mode = __mode?.cstr?.getPointer(memScope).rawValue
        val res = kni_fopen(___filename, ___mode)
        interpretCPointer<FILE>(res)
    }
}

However it’s also possible to get around all this and just call external C functions directly. For example, if you include stdlib.h

external fun malloc(__size: size_t): COpaquePointer?

val pointer = malloc(1024)

C Interop

Pretty much everything that you want to interact with in C code has some kind of Kotlin wrapper class associated with it

This probably stems from the fact that Kotlin was initially designed to be a JVM language. So pointers, references, structs even primitives all have wrapper classes that you need to keep track of. This unfortunately introduces quite a bit of a learning curve.

Taking the readdir function as an example from dirent.h

/* from dirent.h */
fun readdir(arg0: CValuesRef<DIR>?): CPointer<dirent>? {
    return memScoped {
        val _arg0 = arg0?.getPointer(memScope).rawValue
        val res = kni_readdir(_arg0)
        interpretCPointer<dirent>(res)
    }
}

This takes a pointer to a directory struct which contains some pretty fun info about a directory you’ve opened. It returns a pointer Cpointer<dirent> to a struct which has it’s own wrapper class. Getting the object that the pointer is pointing to is simple enough through the pointerVar.pointed member.

class dirent(override val rawPtr: NativePtr) : CStructVar() {
    
    companion object : Type(1048, 8)
    
    var d_ino: __uint64_t
        get() = memberAt<__uint64_tVar>(0).value
        set(value) { memberAt<__uint64_tVar>(0).value = value }
    
    var d_seekoff: __uint64_t
        get() = memberAt<__uint64_tVar>(8).value
        set(value) { memberAt<__uint64_tVar>(8).value = value }
    
    /* Truncated for conciseness */
    
    val d_name: CArrayPointer<ByteVar>
        get() = arrayMemberAt(21)
   
}

How it interprets the underlying byte array is similar to how a struct would in C. So you could access the content directly through the memberAt<Type> function. However it’s much easier to use the generated member variables.

pointerVar.pointed.d_name

Conclusion

In conclusion Kotlin native is what it is; a preview. It is a glimpse into what is possible with Kotlin as a native development language. It’s far from ready for any kind of serious use given the the lack of tooling and nearly non existent documentation. However It’s still a great first step and I’m certainly excited to see where it goes.

Quickstart-Guide-to-Kotlin-Multiplatform

A Quick Start Guide to Kotlin Multiplatform

Kotlin Multiplatform, though still experimental, is a great up-and-coming solution...

Read the article