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.
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.
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.
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