Why can a use block not safely initialize a var?

扶醉桌前 提交于 2019-12-07 09:00:27

问题


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.


回答1:


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.




回答2:


In case an exception occurs while executing it.foo(), the use block will catch the exception, close your autoClosable, and then return. In this case, myVar will be left uninitialized.

This is why the compiler won't let you do what you're trying to do.




回答3:


This is because use is an inline function, which means the lambda body will be inlined to the call-site function, and the actual type of the variable myVar depends on its context.

IF the myVar is used in lambda for reading, the the type is MyType or its supertype. for example:

//      v--- the actual type here is MyType
var myVar: MyType = TODO()

autoClosable.use {
    myVar.todo()
}

IF the myVar is used in lambda for writing, the actual type is an ObjectRef. why? this is because Java don't allow you change the variable out of the annoymous class scope. In fact, myVar is effectively-final. for example:

//  v--- the actual type here is an ObjectRef type.
var myVar: MyType

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

So when the compiler checking at println(myVar), it can't sure the element of the ObjectRef is initialized or not. then a compiler error is raised.

IF you catch anything, the code also can't be compiled, for example:

//  v--- the actual type here is an ObjectRef type.
var myVar: MyType
try {
    autoClosable.use {
        myVar = it.foo()
    }
} catch(e: Throwable) {
    myVar = MyType()
}

//       v--- Error: Variable 'myVar' must be initialized
println(myVar) 

But when the actual type of the myVar is MyType, it works fine. for example:

var myVar: MyType
try {
    TODO()
} catch(e: Throwable) {
    myVar = MyType()
}

println(myVar) // works fine

Why kotlin didn't optimize inline functions to use MyType directly for writing?

the only thing I think, the compiler don't know the myVar whether is used in lambda body of another uninline function in future. or kotlin want to keep semantic consistent for all of the functions.



来源:https://stackoverflow.com/questions/45238746/why-can-a-use-block-not-safely-initialize-a-var

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!