Why is 'by lazy' not happy with in-construction property initialization?

class A {
    val y: Int
    val z: Int by lazy {
        y // ERROR: variable 'y' must be initialized -- Why?
    }
    val w: Int
        get() = y // No error!

    constructor(x: Int) {
        y = x
    }
}

Why do I get an error ‘variable ‘y’ must be initialized’ inside the lazy init?

This post looks like a different manifestation of the same issue. Is this a bug?

I am on Kotlin 1.3.50 in an Android project.

1 Like

Many things happens under the hood when you use delegation. The error here more conceptual than syntactic.

You are creating a class in Java style in Kotlin, and while itself is not a problem, you will need to take care of a lot of stuff that the Kotlin compiler can handle for free if you go for a more Kotlin way:

class A(val y: Int) {
    val z by lazy { y }
    val w
        get() = y
}

This way the compiler will handle any possible instantiation order problem and when the properties gets initialized you will be sure that everything is available from the constructor and ready to use.
I’d really recommend to read the documentation about constructors.

By the way, the order in which you write properties and constructors matters. Move the constructor definition up and it should work.

I can’t use the primary constructor in the real use case. Also, I am not so much looking for an alternative solution, rather an explanation why the code doesn’t compile as written.

Moving the secondary constructor to the top doesn’t change anything.

I did read the documentation about constructors. Which part in particular addresses this issue?

1 Like

The problem is that the compiler cannot figure out when x is accessed within the lazy’s lambda. Does lazy call the lambda immediately or at a later point. If it is immediately, the field needs a value. The compiler cannot know this. (lazy is not a language feature, it is a common API function).

In case of get() the compiler can infer from the shown code alone that it will not be invoked before initialization is completed.

3 Likes

Thanks, this makes sense now.

The other piece of the puzzle that just fell into place for me is that “the code in all initializer blocks including property initializers is executed before the secondary constructor body.” I did quote this from the constructor documentation, but I had to add the italicized part as that is not explicitly stated there. May be an opportunity for improvement, if anyone in charge of the documentation is reading this.

Pulled from above the 3rd code block in the constructors section: “During an instance initialization, the initializer blocks are executed in the same order as they appear in the class body, interleaved with the property initializers”

Still agree it could be more explicit and not hidden in the text

I’m not really sure how you want this to be otherwise. It even comes with a code example that shows the order of execution of the init blocks/property initializers. If you have a better idea of how to show that I’d be interested to see it.
There are a few areas where the kotlin documentation is lacking, but this one is good IMO. That said if you have an idea on how to improve it, go for it. Documentation can always be improved :smile:

a code example that shows the order of execution of the init blocks/property initializers

When I look at the sample code I posted at the beginning of this thread, I see no initializer blocks. Because of this, even though I did read through all the sample code and the part that said "initializer blocks are executed in the same order as they appear in the class body, interleaved with the property initializers”, it didn’t register at first that this part applies to me because hey, I don’t have any initializer blocks.

Same thing with “the code in all initializer blocks is executed before the secondary constructor body.” I have property initializers and a secondary constructor, not initializer blocks and a secondary constructor.

Obviously one can mentally bridge the gap and conclude that if initializer blocks are interleaved with property initializers, and initializer blocks are executed before the secondary constructor body, then property initializers are probably also executed before the secondary constructor body.

The improvement would be to spare the reader this mental effort and have one sentence that covers all three components (initializer blocks, property initializers, and secondary constructors). Specifically, my suggestion is to add the italicized part to this sentence: “the code in all initializer blocks and property initializers is executed before the secondary constructor body.”

Or if you want to get fancy, then add a code example that covers all three components at the same time.

I just created a change to the docs here: https://github.com/Wasabi375/kotlin-web-site/commit/673f7c13e824fdd8be4bdd92b56f3a9457a633a9?short_path=1932db2#diff-1932db2efce252203baef36826a013a4

Unless anyone here has any more feedback I’ll finish up the PR later this evening.

1 Like