Kotlin的crossinline关键字

mel*_*ton 20 kotlin

我已经阅读了这个问题,但我对这个crossinline关键字有一个更基本的问题.我不确定它解决了什么问题以及如何解决它.

来自Kotlin Docs,

请注意,某些内联函数可能会将传递给它们的lambda作为参数调用,而不是直接来自函数体,而是来自另一个执行上下文,例如本地对象或嵌套函数.在这种情况下,lambda中也不允许非本地控制流.为了表明这一点,lambda参数需要用crossinline修饰符标记:

[强调补充]

这句话对我来说很模糊.首先,我在实际描绘"此类情况"的含义时遇到了麻烦.我对这个问题有一个大概的概念,但不能提出一个很好的例子.

其次,短语"表明这一点"可以多种方式阅读.说明什么?这一个特殊情况是不允许的?这允许的吗?允许(或不允许)给定函数定义中的非本地控制流?

简而言之,我很难弄清楚使用它的上下文是什么,使用它与客户端通信的内容,以及应用此关键字的预期结果是什么.

hot*_*key 16

首先,我在实际描绘"此类情况"的含义时遇到了麻烦.我对这个问题有一个大概的概念,但不能提出一个很好的例子.

这是一个例子:

interface SomeInterface {
    fun someFunction(): Unit
}

inline fun someInterfaceBy(f: () -> Unit): SomeInterface { 
    return object : SomeInterface {
        override fun someFunction() = f() 
        //                            ^^^
        // Error: Can't inline 'f' here: it may contain non-local returns. 
        // Add 'crossinline' modifier to parameter declaration 'f'.
    }
}
Run Code Online (Sandbox Code Playgroud)

这里,传递给的函数someInterfaceBy { ... }内联在一个匿名类实现中SomeInterface.每个调用站点的编译someInterfaceBy产生一个具有不同实现的新类someFunction().

要查看可能出现的问题,请考虑致电someInterfaceBy { ... }:

fun foo() {
    val i = someInterfaceBy { return }
    // do something with `i`
}
Run Code Online (Sandbox Code Playgroud)

内联lambda内部return是非本地的,实际上意味着从中返回foo.但是由于lambda没有被调用并且泄漏到对象中i,因此返回foo可能绝对没有意义:如果i.someFunction()(之后是lambda)调用之后foo已经返回甚至是在不同的线程中,该怎么办?

一般来说,"这种情况"意味着inline调用其功能参数的函数不是在它们自己的实体中(有效地,即考虑其他内联函数),而是在它们声明的其他函数内部,例如非内联lambda和匿名对象.


其次,短语"表明这一点"可以多种方式阅读.说明什么?不允许特定情况?这是允许的吗?允许(或不允许)给定函数定义中的非本地控制流?

这正是我在Kotlin语言设计中修复上述问题的方法:每当一个inline函数打算将其功能参数内联到某个地方时,它可能不是就地调用但是后来存储和调用,inline应该标记该函数的参数as crossinline,表示在这里传递的lambdas中不允许非本地控制流.


Yog*_*ity 7

问题:非本地 return

我们先return通过一个简单的例子来理解non-local的问题:

fun doSomething() {
    println("Before lambda")
    doSomethingElse {
        println("Inside lambda")
        return // This is non-local return
    }
    println("After lambda")
}

inline fun doSomethingElse(lambda: () -> Unit) {
    println("Do something else")
    lambda()
}
Run Code Online (Sandbox Code Playgroud)

非本地 return

在上面的代码中,该return语句被称为非本地返回,因为它不是调用它的函数的本地。这意味着该return语句是doSomething()函数的本地语句,而不是调用它的 lambda 函数。因此,它终止当前函数以及最外层函数。

当地的 return

如果你只是想从 lambda 返回,你会说return@doSomethingElse. 这称为本地返回,它是指定它的函数的本地返回

问题

现在这里的问题是编译器跳过了非本地return语句之后的行。反编译的字节码doSomething()如下所示:

public static final void doSomething() {
    System.out.println("Before lambda");
    System.out.println("Doing something else");
    System.out.println("Inside lambda");
}
Run Code Online (Sandbox Code Playgroud)

请注意,没有为该行生成语句println("After lambda")。这是因为我们return在 lambda 中使用了 non-local并且编译器认为return语句之后的代码毫无意义。


解决方案:crossinline关键字

crossinline

这样的情况下(如问题如上所述),所述溶液是不允许的非本地return内部lambda。为了实现这一点,我们将其标记lambdacrossinline

inline fun doSomethingElse(crossinline lambda: () -> Unit) {
    println("Doing something else")
    lambda()
}
Run Code Online (Sandbox Code Playgroud)

非本地return不允许

当您使用crossinline关键字时,您是在告诉编译器,“如果我不小心return在嵌套函数或本地对象中使用了非本地对象,请给我一个错误。”:

fun doSomething() {
    println("Before lambda")
    doSomethingElse {
        println("Inside lambda")
        return                  // Error: non-local return
        return@doSomethingElse  // OK: local return
    }
    println("After lambda")
}
Run Code Online (Sandbox Code Playgroud)

现在编译器按预期生成字节码:

public static final void doSomething() {
    System.out.println("Before lambda");
    System.out.println("Doing something else");
    System.out.println("Inside lambda");
    System.out.println("After lambda");
}
Run Code Online (Sandbox Code Playgroud)

就是这样!希望我让它更容易理解。

  • 确实你做到了!非常感谢 (3认同)
  • 我不认为示例代码说明了这个问题...如果“doSomethingElse”函数在调用“lambda”之后有更多语句,并且非本地“return”导致这些语句被意外跳过,那么这将是一个问题。但该示例在“doSomething”函数中只有“return”之后的语句,并且完全可以跳过“return”之后的语句。换句话说,在当前示例中,“doSomethingElse”函数没有理由关心传递的 lambda 是否为“crossinline”。 (2认同)