Thread-Local Properties in Kotlin

The Kotlin language has a feature called Delegated Properties. It basically lets you delegate a getter (and optionally a setter) to an object that implements the getValue (and optionally the setValue) method.

I haven't had much use of the feature, but I just found something very nifty that could be done with it: create thread-local properties.

Here's an example of what you can do:

import norswap.utils.thread_local.*

class Test
{
    val _i = ThreadLocal.withInitial { 0 }
    val i by _i
    
    val j by thread_local(0)
}

This creates two counters, i and j that are backed by instances of ThreadLocal<Int>. In the first case we specify the instance explicitly, while in the second case the ThreadLocal instance is created implicitly, given a default value (0 here).

And now for the implementation:

package norswap.utils.thread_local
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty

class ThreadLocalDelegate<T> (val local: ThreadLocal<T>)
: ReadWriteProperty<Any, T>
{
    companion object {
        fun <T> late_init ()
            = ThreadLocalDelegate<T>(ThreadLocal())
    }

    constructor (initial: T):
        this(ThreadLocal.withInitial { initial })

    constructor (initial: () -> T):
        this(ThreadLocal.withInitial(initial))

    override fun getValue
            (thisRef: Any, property: KProperty<*>): T
        = local.get()

    override fun setValue
            (thisRef: Any, property: KProperty<*>, value: T)
        = local.set(value)
}

typealias thread_local<T> = ThreadLocalDelegate<T>

operator fun <T> ThreadLocal<T>.provideDelegate
        (self: Any, prop: KProperty<*>)
    = ThreadLocalDelegate(this)

Let's do a quick rundown. The ThreadLocalDelegate class does what it says on the tin: it delegates all attempts to read or write the property to the ThreadLocal instance.

What is more interesting is the different ways you can instantiate the delegate: you can pass it a ThreadLocal instance (primary constructor), an initial value, or a function that computes the initial value. The companion object also has a function late_init() that lets you create a delegate with no initial value.

Now we could use ThreadLocalDelegate like this:

val num by ThreadLocalDelegate(0)
val str by ThreadLocalDelegate<String>.late_init()

But that's quite a mouthful, so there is a typealias thread_local to make things look nicer.

Finally, the provideDelegate operator function tells Kotlin how to create a delegate from a ThreadLocal instance. That's how we could do val i by _i at the top of the post.

Aaand that's pretty much it for today :)