Lateinit val alternative suggestion

The Problem:

I would like to have a Kotlin Language Feature that allows fully type-safe use of an immutable properties in classes that requires an empty constructure plus a method that initialises the lifecycle of the object (for example an Android Activity with the method onCreate).
I read a few Topics mentioning this kind of feature and understand it’s currently not possible to implement it on compiler level.

The Solution Suggested:

The solution would be to add a Delegator into the standard library that implements this behaviour. The Delegator could be added to the kotlin.properties.Delegates object and could look something like this:

class InitOnce<T>(default: T? = null, val allowedChanges: Int = 1): ReadWriteProperty<Any, T> {
	
	private var changeCnt: Int = 0
	private var value: T? = default
	
	override fun getValue(thisRef: Any, property: KProperty<*>): T {
		@Suppress("UNCHECKED_CAST")
		return value as T
	}
	
	override fun setValue(thisRef: Any, property: KProperty<*>, value: T) {
		if (changeCnt >= allowedChanges)
			throw IllegalStateException("Property can only be set $allowedChanges times!")
		this.value = value
		changeCnt++
	}
	
}

I would be very interested in your opinion about this suggestion.
Thank you

Why would you want this?

Usually the constructor is what does once-and-for-all setting of fields. Android is a bit weird, but that’s because they designed the framework in such a way that some classes are initialized by the framework itself.

If you’re forced into a scenario where you need to have classes created by some framework, then I guess your solution should work

I don’t think it is possible in general to do this unless you add a much more complex (stateful) type system. You would need to annotate functions as requiring/updating state and particular states promising certain properties. This would be very invasive to the language, an interesting experiment indeed, but it probably doesn’t belong in Kotlin.

1 Like

If your goal is a lateinit val, you could definitely achieve it with delegation (and overriding the getter).
The most direct method I can think of is a lateinit backing field:

class Foo() {
    private lateinit var _value: String
    val value: String by ::_value
    
    fun initFields() {
        _value = "initialized"
    }
}

fun main() {
//sampleStart
    val f = Foo()
    //println(f.value) // Try uncommenting this line.
    f.initFields()
    println(f.value)
//sampleEnd
}

You could also use a delegate class as you propose and keep a reference to the delegate in order to set the value. This solution almost the same as a backing property but instead of delegating to a property.

If we need more complex assignment logic like limiting to a specific number of internal assignments, we can override the getter or use an intermediate delegate instead of directly delegating to the backing property.

Here’s another possibly simpler take on non-null val that is late initialized:

class Foo() {
    private var _value: String? = null
    val value: String get() = _value ?: error("value not initialized!")
    
    fun initFields() {
        _value = "initialized"
    }
}

fun main() {
//sampleStart
    val f = Foo()
    //println(f.value) // Try uncommenting this line.
    f.initFields()
    println(f.value)
//sampleEnd
}
2 Likes