考虑以下重载
Reader.useLines()
的 Kotlin 代码。第一个扩展仅调用 Reader.useLines()
并具有完全相同的合约,第二个扩展使用某些谓词过滤行序列并将过滤后的序列传递给消费者:
import java.io.Reader
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind.EXACTLY_ONCE
import kotlin.contracts.contract
private val prefixes: Array<out String> = arrayOf(
"foo",
"bar",
"baz",
)
private fun String.hasPrefix(): Boolean =
prefixes.any { prefix ->
this.startsWith(prefix)
}
@OptIn(ExperimentalContracts::class)
fun <T> Reader.useLinesA(
block: (Sequence<String>) -> T,
): T {
contract {
callsInPlace(block, EXACTLY_ONCE)
}
return block.let(this::useLines)
}
@OptIn(ExperimentalContracts::class)
fun <T> Reader.useLinesB(
block: (Sequence<String>) -> T,
): T {
contract {
callsInPlace(block, EXACTLY_ONCE)
}
return { lines: Sequence<String> ->
lines.filterNot(String::hasPrefix).let(block)
}.let(this::useLines)
}
过去的代码在 Kotlin 1.9 中编译得很好,但现在我收到以下警告:
fun <T> Reader.useLinesA(
block: (Sequence<String>) -> T,
): T {
contract { // Leaked in-place lambda 'block: (Sequence<String>) -> T'.
callsInPlace(block, EXACTLY_ONCE)
}
return block.let(this::useLines) // Leaked in-place lambda 'block: (Sequence<String>) -> T'.
}
fun <T> Reader.useLinesB(
block: (Sequence<String>) -> T,
): T {
contract { // Leaked in-place lambda 'block: (Sequence<String>) -> T'.
callsInPlace(block, EXACTLY_ONCE)
}
return { lines: Sequence<String> ->
lines.filterNot(String::hasPrefix).let(block) // Leaked in-place lambda 'block: (Sequence<String>) -> T'.
}.let(this::useLines)
}
此外,对于每个
contract()
调用(尽管它们看起来完全正确)我收到:
Wrong invocation kind 'EXACTLY_ONCE' for 'block: (Sequence<String>) -> T' specified, the actual invocation kind is 'ZERO'.
那么上面的代码有什么问题吗?
任何人都可以解释为什么会出现内存泄漏,以及如何重构代码来避免它们吗?
最后,为什么
block
会被调用 ZERO
次,如果它实际上被称为 EXACTLY_ONCE
,即使对于空序列也是如此?
这些是已知的 Kotlin 问题:问题 1(泄漏的就地 lambda)、问题 2(称为 EXACTLY_ONCE)。尽管出现了警告,但它们是误报。
第一个问题的修复应该包含在 2.0.0-RC1(2.0.0-RC1-37)、2.0.20-Beta1(2.0.20-dev-121) 中的某个位置。不过,我不确定稳定版本中是否已经包含该修复程序 - 您只需自行检查即可。所以要么更新,要么等待,要么忽略。
第二个问题的修复已部分完成 2.0.0-Beta3(有一个第二部分),但看起来没有包含它的构建(也许稳定的版本确实包含它 - 我不确定)。它的目标版本应该是 2.1.0 中的某个地方。