保护条件引发编译器错误,讨论关闭

Guard condition provokes compiler error that talks about closure

考虑这段代码:

class Foo {
    var a: Int
    var b: Int

    init(a: Int, b: String?) throws {
        self.a = a

        guard self.a > 0 else {
            throw "Too little a!"
        }
        self.b = self.a
    }
}

extension String: Error {}

非常荒谬,但关键是它编译得很好。 现在将守卫替换为:

guard b == nil || self.a > 0 else {

不是我们遇到编译器错误!

Error: 'self' captured by a closure before all members were initialized

我个人在任何地方都看不到闭包。如果 guard 条件是复合表达式,编译器是否将它们翻译成闭包,从而引入错误(如果 闭包,这将是正确的)?

错误或功能?

这是 Swift 3.0.2.

这个问题,正如 Martin , is that the || operator is implemented with an @autoclosure 第二个参数所解释的那样,为了允许进行短路评估(仅当左侧表达式的计算结果为 false 时,才需要对右侧表达式进行评估).

因此在表达式中

b == nil || self.a > 0

self.a > 0 隐含在 () -> Bool 闭包中。这是有问题的,因为它需要捕获 self,以便在应用闭包时可以访问 a

但是,在初始化程序中,Swift 严格限制了在 self 完全初始化之前您可以对其执行的操作。这些限制之一是无法被闭包捕获——这就是为什么会出现编译器错误。

虽然实际上,闭包 { self.a > 0 } 在完全初始化之前捕获 self 并没有错,因为闭包所做的只是访问它上面的 a,这已经初始化。因此,这实际上只是一个边缘案例(there's an open bug report),我希望在该语言的未来版本中能够解决这个问题。

在那之前,如 Martin 所示,一种解决方案是使用临时局部变量创建 self.a 值的副本,避免捕获 self在闭包中:

class Foo {
    var a: Int
    var b: Int

    init(a: Int, b: String?) throws {

        // some computation meaning that a != self.a
        self.a = a * 42

        // temporary local variable to be captured by the @autoclosure.
        let _a = self.a
        guard b == nil || _a > 0 else {
            throw "Too little a!"
        }

        self.b = self.a
    }
}

extension String: Error {}

显然,这里假设self.a != a,否则你可以在guard条件下直接引用a而不是self.a