Compilation error: Smart cast to '<type>' is not possible because '<variable>' is a local variable that is captured by a changing closure
To simplify my real use case, suppose I want to find the maximum number in the 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 casting in "Int" is not possible because "max" is a local variable that is captured by a changing closure
Why does the closing variable prevent the smart throw from working in this example?
In the general case, when a variable to be changed is fixed when the lambda function is closed, smart casts are not applicable to this variable both inside the lambda and in the declaration area after creating the lambda.
This is due to the fact that the function can go out of its covering area and can be performed later in a different context, possibly several times and, possibly, in parallel. As an example, consider the hypothetical function List.forEachInParallel { ... } , which performs the specified lambda function for each element of the list, but in parallel.
The compiler must generate code that will remain true even in this serious case, therefore, it does not make the assumption that the value of the variable remains unchanged after a zero check and, therefore, cannot use smart.
However, List.forEach is peculiar because it is inline . The body of the built-in function and the body of its functional parameters (if only the parameter has noinline or crossinline ) are not inserted on the call site, so the compiler can talk about the code in the lambda passed as an argument to the built-in as if it were written directly in the body of the calling method, what makes skill possible.
It may, but at present it is not, simply because this function has not yet been implemented. There is an open problem for him: KT-7186 .
The problem is that foreach creates several closures, each of which gets access to the same max , which is var .
What happens if max was set to null in another closure after checking max == null , but before it > max ?
Since each closure can theoretically work independently (potentially on multiple threads), but everyone has access to the same max , you cannot guarantee that it will not change at run time.
This seems like a compiler error.
If the built-in lambda parameter in forEach was marked as crossinline , then I would expect a compilation error due to the possibility of simultaneous calls to the lambda expression.
Consider the following forEach implementation:
inline fun <T> Iterable<T>.forEach(crossinline action: (T) -> Unit): Unit { val executorService: ExecutorService = ForkJoinPool.commonPool() val futures = map { element -> executorService.submit { action(element) } } futures.forEach { future -> future.get() } } The above implementation will not be able to compile without the crossinline modifier. Without it, a lambda can contain non-local returns , which means that it cannot be used simultaneously.
I suggest creating a problem: Kotlin (KT) | YouTrack .