You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

升级至Kotlin 2.0后出现“Leaked in-place lambda”警告问题排查

Kotlin 2.0中Reader.useLines重载的合约与Lambda泄漏问题解答

以下是重载Reader.useLines()的Kotlin代码:useLinesA直接转发原方法调用,useLinesB先过滤序列再传递给消费者。该代码在Kotlin 1.9可正常编译,但升级至2.0后出现**"Leaked in-place lambda"警告**,同时合约检查提示**"指定EXACTLY_ONCE但实际调用次数为ZERO"**。

代码示例

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

警告与错误信息

Lambda泄漏警告

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

合约校验错误

Wrong invocation kind 'EXACTLY_ONCE' for 'block: (Sequence<String>) -> T' specified, the actual invocation kind is 'ZERO'.

1. 代码存在的核心问题

代码错误地为传入的block lambda添加了callsInPlace(EXACTLY_ONCE)合约声明,但实际逻辑是将block传递给Reader.useLines执行——这违反了Kotlin 2.0对callsInPlace合约的严格约束:被标记为callsInPlace的lambda必须在当前函数的调用栈内直接执行,不能被传递到其他函数中执行。Kotlin 1.9的合约检查没有这么严格,所以之前能通过,但2.0强化了静态分析,直接触发警告和错误。

2. "Leaked in-place lambda"的原因及修复方案

原因

这里的"Leaked"并非传统意义上的内存泄漏,而是Kotlin合约检查中的术语:指被标记为callsInPlace的lambda被传递到了当前函数作用域之外(这里是传递给Reader.useLines),当前函数无法保证该lambda的执行时机和上下文符合callsInPlace的约定(比如必须在当前栈内执行一次)。

修复方案

直接移除自定义的contract块即可:

  • Reader.useLines本身已经带有正确的callsInPlace合约声明,不需要重复添加;
  • 我们的封装函数只是转发或包装lambda,并没有直接执行block,没有资格声明callsInPlace合约。

修复后的代码示例:

import java.io.Reader

private val prefixes: Array<out String> = arrayOf(
    "foo",
    "bar",
    "baz",
)

private fun String.hasPrefix(): Boolean =
    prefixes.any { prefix ->
        this.startsWith(prefix)
    }

fun <T> Reader.useLinesA(
    block: (Sequence<String>) -> T,
): T {
    return block.let(this::useLines)
}

fun <T> Reader.useLinesB(
    block: (Sequence<String>) -> T,
): T {
    return { lines: Sequence<String> ->
        lines.filterNot(String::hasPrefix).let(block)
    }.let(this::useLines)
}

3. 为何实际调用block一次却提示ZERO次

Kotlin的合约静态分析器只能追踪当前函数内部的代码逻辑,无法深入到其他函数(比如Reader.useLines)的实现中。我们的自定义函数里没有直接调用block,只是把它传递给了useLines,所以分析器认为当前函数内block的调用次数是ZERO,但我们的合约声明了EXACTLY_ONCE,两者冲突,因此触发错误提示。


内容的提问来源于stack exchange,提问作者Андрей Щеглов

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 14:56:07