crossinline 是一个用于 Kotlin 高阶函数中函数类型参数的修饰符。它的主要作用是允许在传递给高阶函数的 lambda 表达式中使用非局部返回 (non-local returns),同时确保该 lambda 表达式不会被存储并在稍后的时间点被调用(即禁止非局部控制流传递给另一个执行上下文)。让我们分解一下这个概念:
1. 高阶函数 (Higher-Order Functions):
高阶函数是指接收一个或多个函数作为参数,或者返回一个函数的函数。
fun higherOrderFunction(block: () -> Unit) {
println("Before block")
block() // 调用传递进来的 lambda
println("After block")
}
2. Lambda 表达式中的返回 (Returns in Lambdas):
- 局部返回 (Local Return): 默认情况下,在 lambda 表达式中使用 return 关键字,它会从该 lambda 表达式本身返回,而不是从包含该 lambda 调用的外层函数返回。
fun example() {
listOf(1, 2, 3).forEach {
if (it == 2) {
// return // 编译错误!不能在这里直接从 example() 返回
return@forEach // 正确:从 forEach 的 lambda 返回
}
println(it)
}
println("End of example") // 如果上面是 return@forEach,这里会执行
}
- 非局部返回 (Non-Local Return): 在某些情况下,我们希望从 lambda 表达式中直接返回并退出包含该 lambda 调用的外层函数。这通常只在 inline 函数的 lambda 参数中才被允许。
inline fun inlineHigherOrderFunction(block: () -> Unit) {
println("Before block")
block()
println("After block") // 如果 block() 中有非局部返回,这行可能不会执行
}
fun outerFunction() {
println("Start of outerFunction")
inlineHigherOrderFunction {
println("Inside lambda")
if (true) { // 某个条件
return // 非局部返回!这里会直接从 outerFunction() 返回
}
println("This won't be printed if return happens")
}
println("End of outerFunction") // 如果 lambda 中发生了非局部返回,这行不会执行
}
3. inline 函数:
当一个高阶函数被标记为 inline 时,编译器会将该函数的代码以及传递给它的 lambda 表达式的代码直接内联到调用处。这可以减少函数调用的开销,并且允许在 lambda 表达式中使用非局部返回。
4. crossinline 的作用:
现在我们来看 crossinline。当你有一个 inline 高阶函数,并且它的某个函数类型的参数不应该允许非局部返回,但你又想让这个 lambda 能够访问其闭包中的非局部控制流(比如 return 到外层函数,但不是直接从 lambda 自身返回),或者这个 lambda 可能会被传递到另一个执行上下文(例如,在一个嵌套的 lambda 或者一个对象表达式中被调用),这时就需要使用 crossinline。核心思想:crossinline 告诉编译器:“这个 lambda 参数是内联的,但是不允许它直接进行非局部返回。然而,它内部的代码仍然可以包含非局部返回的意图,只是不能直接从这个 lambda 跳出。”为什么需要 crossinline?想象一个场景,你的 inline 函数接收一个 lambda,但这个 lambda 被传递给另一个非 inline 的函数,或者被一个对象表达式捕获并在稍后执行。
inline fun <T> mySynchronized(lock: Any, block: () -> T): T {
synchronized(lock) {
return block() // 如果 block 允许非局部返回,这里可能出问题
}
}
fun problematicExample() {
println("Start")
mySynchronized(Any()) {
println("Inside lambda")
// 如果这里允许 return,它会尝试从 mySynchronized 返回,
// 而不是从 problematicExample 返回,这在 mySynchronized
// 的设计中可能是不期望的,因为它是在 synchronized 块内部。
// return // 如果没有 crossinline,这可能导致编译错误或意外行为
"Result"
}
println("End")
}
如果 block 参数没有 crossinline,并且它内部尝试进行非局部返回,编译器可能会报错,因为它不知道如何处理这种跨越内联边界的非局部返回,特别是当 lambda 的执行被延迟或转移到其他上下文时。
使用 crossinline 的效果:
当你在 inline 函数的函数类型参数上使用 crossinline 时:
- 禁止非局部返回: 该 lambda 表达式内部不能直接使用 return 来退出外层函数。如果你尝试这样做,编译器会报错。
- 仍然是内联的: lambda 的代码仍然会被内联到调用处,享受 inline 带来的性能优势。
- 允许传递到其他执行上下文: 你可以安全地将这个 crossinline lambda 传递给另一个函数(即使是非 inline 的)或者在一个对象表达式中捕获它,并在稍后调用它。
看以下代码片段中的例子:
public inline fun <T, R> CYSafeFlow<T>.transform(
crossinline transform: suspend CYFlowCollector<R>.(value: T) -> Unit
): CYSafeFlow<R> = CYSafeFlow { // Note: safe flow is used here, because collector is exposed to transform on each operation
collect { value ->
// kludge, without it Unit will be returned and TCE won't kick in, KT-28938
return@collect transform(value) // 注意这里是 return@collect,是局部返回
}
}
在这个 transform 函数中:
- transform 是一个 suspend 函数类型的参数。
- 它被标记为 crossinline。
- 在 CYSafeFlow 的构造函数 lambda 内部,transform 被调用。
CYSafeFlow 的构造函数 lambda 本身创建了一个新的执行上下文(尽管它最终会在 collect 时被调用)。
为什么这里使用 crossinline?
transform lambda 被传递并在另一个 lambda 内部调用: transform lambda 被传递到 CYSafeFlow 的构造函数 lambda 中,并在 collect 的 lambda 内部被调用 (return@collect transform(value))。
防止非局部返回破坏 transform 的封装: 如果 transform lambda 能够进行非局部返回,它可能会意外地跳出 CYSafeFlow 的构造逻辑或者 collect 的逻辑,这可能不是期望的行为。crossinline 保证了 transform lambda 本身不能直接进行非局部返回,从而维护了 transform 操作符的封装性和可预测性。
仍然希望内联的好处: transform 函数本身是 inline 的,使用 crossinline 允许 transform lambda 的主体也被内联,以获得性能优势。
总结 crossinline 的使用场景:
当你满足以下所有条件时,应该考虑在 inline 函数的函数类型参数上使用 crossinline:1.该函数是 inline 的。2.该函数类型参数对应的 lambda 不应该允许非局部返回 (直接 return 退出外层函数)。3.该 lambda 可能会被传递到另一个执行上下文(例如,在另一个 lambda 中被调用、被对象表达式捕获并在稍后执行等)。通过使用 crossinline,你可以确保 lambda 的内联优势,同时防止不安全的非局部返回破坏代码的结构和行为