Proof Of Concept to implement the RAII pattern in Kotlin using a compiler plugin.
Java utilizes garbage collection for memory management. When an object is no longer needed, it is automatically freed at some point. However, we have limited influence on how and when.
But how do we handle non-heap resources such as files, socket connections, or even hardware? How should the garbage collector evaluate whether the graphics card still has enough free memory?
We are unable to use java.lang.Object.finalize().
The finalization mechanism is problematic and marked as deprecated.
There are no guarantees about the order and timing of finalization.
The finalize method may not be called until after an indefinite delay, if at all.
The documentation points out that non-heap resources should be released by implementing java.lang.AutoClosable.
Java (try (var r = new Resource()) { … }) and Kotlin (Resource().use { … }) provide tools to automatically close or release instances of AutoClosable.
However, this is limited to the scope of a single function.
If we have many non-heap resources associated with the lifecycle of objects, then they need to be released explicitly.
This is error-prone and can lead to leaks.
"Resource Acquisition Is Initialization" (RAII) is a resource management strategy invented by Bjarne Stroustrup for C++. Rust has adopted RAII as a core language concept through its ownership system and the Drop trait, making deterministic resource cleanup a fundamental part of the language. In both languages, each object is responsible for managing its own resources, ensuring that any resources are released as early as possible.
/**
* A temporary file as an example for a non-heap
* resource.
*/
class ExternalResource : AutoCloseable {
private val tempFile = createTempFile()
init {
println("$tempFile has been created.")
}
/**
* The implementation of [AutoCloseable.close] is
* responsible for freeing the resource. In this
* case the file needs to be removed.
*/
override fun close() {
tempFile.deleteExisting()
println("$tempFile has been deleted.")
}
}
/**
* A class which uses other resources.
*
* It extends the [AutoCloseable] interface, but does
* implement [AutoCloseable.close]. This is done
* automatically by the compiler plugin.
*/
class ResourceOwner : AutoCloseable {
@Scoped
private val firstResource = ExternalResource()
@Scoped
private val secondResource = ExternalResource()
}
fun main() {
@Scoped val owner = ResourceOwner()
println("Hello World!")
// owner.close() is called automatically when the function ends
}/tmp/2214526001872441198.tmp has been created. /tmp/15561580686778472.tmp has been created. Hello World! /tmp/15561580686778472.tmp has been deleted. /tmp/2214526001872441198.tmp has been deleted.
The plugin generates ResourceOwner.close() which closes secondResource and firstResource in reverse declaration order. The @Scoped local variable in main ensures owner.close() is called automatically when the function ends.
IntelliJ IDEA may show errors for missing close() methods that are generated by the compiler plugin. To fix this, set the following registry flag:
-
Open the registry:
Help→Find Action…→ typeRegistry… -
Find the key
kotlin.k2.only.bundled.compiler.plugins.enabled -
Set it to
false
See KTIJ-29248 for details.