June 25, 2023

Kotlin: Alloc, AllocArray, and Memscoped

My approach to learning a language is to dive in headfirst and then try to sort things out. I am working with Kotlin Native, and my idea of a first project was to learn how to read lines from an ascii file.

The game for reading from a file with Kotlin native is to use what they call "cinterop", which gives you things like fopen() and fgets() -- and presumably fclose() from the posix C library.

By following an online example, I try to use allocArray() to get a buffer to pass to fgets to hold the line I want to read. I get:

kotlinc dict.kt -o dict
dict.kt:11:18: error: unresolved reference: allocArray
    val buffer = allocArray(readBufferLength)
Searching on this error, I learn that Alloc() (and its brother AllocArray() presumably) can only be called from within a Memscoped() block. This is new to me, but if I am guessing right, this is a good thing and is a scheme for ensuring that memory gets freed.

The error is not very helpful (in fact confusing, if not misleading). Somehow they have things rigged up so that Alloc() is only provided if you are inside a Memscoped block, but once you know, then you know.

Here it is, my first kotlin native program. I allocate a pretty big line buffer -- but no harm done.

import platform.posix.*
import kotlinx.cinterop.*

val path = "dictionary.txt"

fun main()
{
    val readBufferLength = 64 * 1024

    println ( "File: $path" )

    val file = fopen ( path, "r" ) ?:
        throw IllegalArgumentException("Cannot open input file $path")

    memScoped {
	val buffer = allocArray(readBufferLength)
	var line = fgets(buffer, readBufferLength, file)?.toKString()
	print ( line )
    }
}
I don't like getting a .kexe file as the result of my compile, so I have a Makefile like so:
# Build a simple Kotlin native program

dict:	dict.kt
	kotlinc dict.kt -o dict
	mv dict.kexe dict
I "enhance" my program to loop through the entire file, which provides some lessons. I'll note that in Kotlin, you don't return from main() with a value. main() returns the "Unit" which is sort of the Kotlin way of setting up a void function. Also Kotlin doesn't have the C style "for" loop, so you can't use the "for (;;)" idiom to get an infinite loop.
import platform.posix.*
import kotlinx.cinterop.*

val path = "dictionary.txt"

fun main()
{
    val readBufferLength = 64 * 1024
    var count = 0

    println ( "File: $path" )

    val file = fopen ( path, "r" )
    if ( file == null ) {
	println ("Cannot open input file $path")
	return
    }

    memScoped {
	val buffer = allocArray(readBufferLength)
	while ( true ) {
	    var line = fgets(buffer, readBufferLength, file)?.toKString()
	    if ( line == null )
		break
	    count++
	    // print ( line )
	}
    }
    println ( "$count lines in file" )
}
Note the nice way you can interpolate values into strings. Also note that you need not write "void fun main ( void )". This is yet another example of kotlin striving to be succinct. Notice also that I have relied upon type inference for every variable. Kotlin is strictly typed, but it can figure out what types I want from the context.
Have any comments? Questions? Drop me a line!

Adventures in Computing / tom@mmto.org