crossinline 关键字

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 时:

  1. 禁止非局部返回: 该 lambda 表达式内部不能直接使用 return 来退出外层函数。如果你尝试这样做,编译器会报错。
  2. 仍然是内联的: lambda 的代码仍然会被内联到调用处,享受 inline 带来的性能优势。
  3. 允许传递到其他执行上下文: 你可以安全地将这个 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?

  1. transform lambda 被传递并在另一个 lambda 内部调用: transform lambda 被传递到 CYSafeFlow 的构造函数 lambda 中,并在 collect 的 lambda 内部被调用 (return@collect transform(value))。

  2. 防止非局部返回破坏 transform 的封装: 如果 transform lambda 能够进行非局部返回,它可能会意外地跳出 CYSafeFlow 的构造逻辑或者 collect 的逻辑,这可能不是期望的行为。crossinline 保证了 transform lambda 本身不能直接进行非局部返回,从而维护了 transform 操作符的封装性和可预测性。

  3. 仍然希望内联的好处: transform 函数本身是 inline 的,使用 crossinline 允许 transform lambda 的主体也被内联,以获得性能优势。

总结 crossinline 的使用场景:

当你满足以下所有条件时,应该考虑在 inline 函数的函数类型参数上使用 crossinline:1.该函数是 inline 的。2.该函数类型参数对应的 lambda 不应该允许非局部返回 (直接 return 退出外层函数)。3.该 lambda 可能会被传递到另一个执行上下文(例如,在另一个 lambda 中被调用、被对象表达式捕获并在稍后执行等)。通过使用 crossinline,你可以确保 lambda 的内联优势,同时防止不安全的非局部返回破坏代码的结构和行为

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容