Kotlin的crossinline关键字

时间:2017-08-18 16:05:54

标签: kotlin

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

来自the Kotlin Docs

  

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

[强调补充]

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

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

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

2 个答案:

答案 0 :(得分:15)

  

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

以下是一个例子:

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'.
    }
}

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

要查看可能出现的问题,请考虑拨打someInterfaceBy { ... }

fun foo() {
    val i = someInterfaceBy { return }
    // do something with `i`
}

在内联lambda中,return is non-local实际上意味着foo 返回。但由于lambda未被调用并泄漏到对象i中,foo 返回可能绝对没有意义:如果i.someFunction()(因此lambda)会怎么样?在foo已经返回后甚至在另一个帖子中调用?

通常,'此类情况'意味着inline函数调用其功能参数而不是在自己的身体中(有效地,即考虑其他内联函数)但在其他一些函数中声明,就像在非内联lambda和匿名对象中一样。

  

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

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

答案 1 :(得分:5)

问题:非本地return

我们先通过一个简单的例子来理解非本地return的问题:

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

非本地return

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

本地return

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

问题

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

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

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


解决方案:crossinline 关键字

crossinline

这种情况(如上面提到的问题),解决方案是禁止 return 中的非本地 lambda。为此,我们将 lambda 标记为 crossinline

inline fun doSomethingElse(crossinline lambda: () -> Unit) {
    println("Doing something else")
    lambda()
}

非本地 return 不允许

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

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

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

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

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