Why can't one use lateinit with nullable types?

If you write

class Foo {
    lateinit var foo: String?
}

you’ll get

‘lateinit’ modifier is not allowed on properties of nullable types

Is there a particular reason this is disallowed?

In many cases null is a correct value for a variable, and so can not signify an uninitialized state. So I think it would be nice if I could use lateinit with nullable types. For instance, consider this kind of code:

class Foo {
    var foo: String? = null

    fun initFoo() {
        foo = if (something) "foo" else null
    }

    fun useFoo() {
        if (foo == null) bar() else baz()
    }
}

It this example it would be incorrect to call useFoo() before initFoo(). But this can happen, and in this case useFoo() will call bar() instead of crashing. And what if bar() reelects Trump or something? So to avoid that, you have to use a bogus object to signify a “nullish” value, or use an “initialized” flag…

I understand that internally null represents an uninitialized lateinit state; but can’t a different kind of marker be used instead?

JVM guarantees the types of everything (just not nullity), so Kotlin can’t just assign some random token object under the covers. A null value for non-null types is the only case where Kotlin can assign a value that is not of the correct Kotlin type but is acceptable to the JVM.

Honestly, I view the ability to check if lateinit vars are initialized as only marginally valuable in general and have never used it personally.

lateinit is super helpful in cases where real initialization happens after construction and its impossible to provide any value at all of the right type before the initialization method is called.

On Android, I can’t have an Activity member variable of type View without lateinit. It’s impossible to create a View without the Context that is provided to onCreate. Many of the vars I might use on Android fall in this category. I don’t need a special uninitialized value to check for, I just can’t create any value at all to use as a placeholder until onCreate is called.

I’m not quite sure if this is right, for instance here’s a complete and runnable example of a property similar to Delegates.notNull() but nullable—and it works fine in JVM too:

import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
import java.lang.Exception

object Missing

private class Property<T> : ReadWriteProperty<Any?, T> {
    private var value: T = Missing as T

    override fun getValue(thisRef: Any?, property: KProperty<*>): T {
        if (value === Missing) {
            throw IllegalStateException("Property ${property.name} should be initialized before get.")
        } else {
            return value
        }
    }

    override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
        this.value = value
    }
}

object Test {
    var foo by Property<String?>()
}

fun main() {
    try {
        Test.foo
    } catch (e: IllegalStateException) {
        println("error while getting foo: $e")
        println("setting foo to null")
        Test.foo = null
    }

    if (Test.foo == null) {
        println("foo is null, setting to 'foo'")
        Test.foo = "foo"
    }

    println("foo is '${Test.foo}'")
}

This prints:

error while getting foo: java.lang.IllegalStateException: Property foo should be initialized before get.
setting foo to null
foo is null, setting to ‘foo’
foo is ‘foo’

I agree that their use cases are rare, perhaps very rare, but IMO sometimes you do need them and the alternatives are just too complex and ugly

That only works because the type of your lateinit property is an unbounded type parameter, which means it gets erased to Object when compiled. If you were to try the same with any other type, you would get either a compiler error, or a ClassCastException during construction at runtime.

Are you saying that this kind of trick would be impossible to use with lateinit? I don’t know the internals of JVM. I assumed that if I can make this work using a simple cast, one could make it work even better via custom bytecode

JVM performs type checks at runtime, which makes it impossible to either assign a value to a field of incompatible type, or call a method with parameters of incompatible types. If you were to write bytecode by hand that tries to do so, then the JVM would throw an error, either during bytecode verification or when executing the violating method.

Well, if type erasure is the only trick that can do it, why not use it? It seems that it works well, even in my silly example

In that case, every lateinit property with a nullable type (if you were to also include non-nullable properties, it would be a breaking change) would have to compiled to a field of type Object. This would affect a lot of libraries that use reflection to check the type of the field. Since field injection is a rather significant use case for lateinit properties, this is a quite significant limitation.

I see. While this limitation is, in fact, limiting, I’m not sure if anything is gained by prohibiting nullable lateinit:

  • field injection libraries can simply refuse to work with nullable fields
  • it wouldn’t break any existing code since nullable lateinit vars currently do not exist
  • it sounds like the limiting factor here is JVM and while it is big it’s not the whole Kotlin

…well, apart from not having to write the code