为什么使用块不能安全地初始化一个var?

为什么这给编译错误?

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

也许编译器只是把{ myVar= it.foo() }作为一个函数传递给另一个函数,并且不知道什么时候,甚至是否会被执行?

但是由于use不仅仅是一个函数,而且Kotlin已经替代了Java的“试用资源”,关于它的一些特殊的知识将是适当的,不是吗? 现在,我不得不用一些虚拟的价值初始化myVar ,这完全不符合Kotlin的精神。

由于use { ... }不是一个语言结构,而只是一个库函数 ,所以编译器不知道(而且目前也没有努力certificate)你传递的lambda是被执行的 。 因此禁止使用可能未被初始化的variables。

例如,比较你的代码到这个函数调用。 没有额外的代码分析,它们对于编译器是相同的:

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

为了绕过这个限制,你可以使用从use返回的值(这是块返回的值)来初始化你的variables:

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

如果你也想处理它可能抛出的exception,那么使用try作为expression式 :

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

理论上,内联函数实际上可以检查一次调用一个lambda函数,如果Kotlin编译器可以这样做,它将允许您的用例和其他一些。 但是这个还没有实施。

如果在执行it.foo()时发生exception, use块将捕获exception,关闭你的autoClosable ,然后返回。 在这种情况下, myVar将保持未初始化。

这就是为什么编译器不会让你做你想做的事情。

这是因为use内联函数 ,这意味着lambda体将内联到call-site函数,并且variablesmyVar的实际types取决于其上下文。

如果在lambda中使用myVar进行读取,则types为MyType或其超types。 例如:

 // v--- the actual type here is MyType var myVar: MyType = TODO() autoClosable.use { myVar.todo() } 

如果 myVar在lambda中用于写入,则实际types是ObjectRef 。 为什么? 这是因为Java不允许您将variables从恼人的类范围中更改。 实际上, myVar有效的 。 例如:

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

所以当编译器检查println(myVar) ,不能确定ObjectRef的元素是否被初始化。 那么会引发编译器错误。

如果你抓住任何东西,代码也不能编译,例如:

 // 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) 

但是,myVar的实际types是MyType ,它工作正常。 例如:

 var myVar: MyType try { TODO() } catch(e: Throwable) { myVar = MyType() } println(myVar) // works fine 

为什么 kotlin没有优化内联函数来直接使用MyType进行书写?

我唯一想的是,编译器将来不知道myVar是否用在另一个uninline函数的lambda体中。 或者kotlin想保持所有function的语义一致。