Search code examples
kotlinautocloseable

Why can a use block not safely initialize a var?


Why does this give a compile error?

val autoClosable = MyAutoClosable()
var myVar: MyType
autoClosable.use {
    myVar= it.foo()
}
println(myVar) // Error: Variable 'myVar' must be initialized

Maybe the compiler just sees { myVar= it.foo() } as a function that is passed to another function and has no knowledge about when or even if it will be executed?

But since use is not just a function but Kotlin's replacement for Java's try-with-resource, some special knowledge about it would be appropriate, wouldn't it? Right now, I am forced to initialize myVar with some dummy value, which is not in the spirit of Kotlin at all.


Solution

  • Since use { ... } is not a language construct but is just a library function, the compiler doesn't know (and, currently, does not make effort to prove) that the lambda you pass is ever executed. Therefore the usage of the variable that might not be initialized is prohibited.

    For example, compare your code to this function call. Without additional code analysis, they are identical for the compiler:

    inline fun ignoreBlock(block: () -> Unit) = Unit
    
    var myVar: MyType
    ignoreBlock { myVar = it.foo() }
    println(myVar) // Expectedly, `myVar` stays uninitialized, and the compiler prohibits it
    

    To bypass this restriction, you can use the value returned from use (this is the value your block returns) to initialize your variable:

    val myVar = autoClosable.use {
        it.foo()
    }
    

    And if you also want to handle the exception it might throw, then use try as an expression:

    val myVar = try {
        autoClosable.use {
            it.foo()
        }
    } catch (e: SomeException) {
        otherValue   
    }
    

    Theoretically, inline functions can actually be checked to invoke a lambda exactly once, and if the Kotlin compiler could do that, it would allow your use case and some others. But this has not been implemented yet.