Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Compilation error: Smart cast to '<type>' is impossible, because '<variable>' is a local variable that is captured by a changing closure

Tags:

casting

kotlin

To simplify my real use case, let's suppose that I want to find the maximum number in a list:

var max : Int? = null listOf(1, 2, 3).forEach {     if (max == null || it > max) {         max = it     } } 

However, compilation fails with the following error:

Smart cast to 'Int' is impossible, because 'max' is a local variable that is captured by a changing closure

Why does a changing closure prevent smart cast from working in this example?

like image 908
Dragan Bozanovic Avatar asked Aug 12 '16 23:08

Dragan Bozanovic


2 Answers

In general, when a mutable variable is captured in a lambda function closure, smart casts are not applicable to that variable, both inside the lambda and in the declaring scope after the lambda was created.

It's because the function may escape from its enclosing scope and may be executed later in a different context, possibly multiple times and possibly in parallel. As an example, consider a hypothetical function List.forEachInParallel { ... }, which executes the given lambda function for each element of the list, but in parallel.

The compiler must generate code that will remain correct even in that severe case, so it doesn't make an assumption that the value of variable remains unchanged after the null check and thus cannot smart cast it.

However, List.forEach is quite different, because it is an inline function. The body of an inline function and the bodies of its functional parameters (unless the parameter has noinline or crossinline modifiers) are inlined at the call site, so the compiler could reason about the code in a lambda passed as an argument to inline function as if it was written directly in the calling method body making the smart cast possible.

It could, but currently, it doesn't. Simply because that feature is not implemented yet. There is an open issue for it: KT-7186.

like image 74
Ilya Avatar answered Sep 19 '22 12:09

Ilya


Thanks to Ilya for the detailed explanation of the problem! You can use the standard for(item in list){...} expression like this:

var max : Int? = null val list = listOf(1, 2, 3) for(item in list){     if (max == null || item > max) {         max = item     } } 
like image 25
Ivo Stoyanov Avatar answered Sep 20 '22 12:09

Ivo Stoyanov