Consider this code:
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 {}
Pretty non-sensical, but the point is that it compiles fine. Now replace the guard with:
guard b == nil || self.a > 0 else {
Not we get a compiler error!
Error: 'self' captured by a closure before all members were initialized
I for one don't see a closure anywhere. Is the compiler translating guard
conditions into closures if they are compound expressions, thus introducing the error (which would be correct if there was a closure)?
Bug or feature?
This is with Swift 3.0.2.
The problem, as explained by Martin in this Q&A, is that the ||
operator is implemented with an @autoclosure
second parameter, in order to allow for short-circuit evaluation (the right hand expression need only be evaluated if the left hand expression evaluates to false
).
Therefore in the expression
b == nil || self.a > 0
self.a > 0
is implicitly wrapped in a () -> Bool
closure. This is problematic, because it requires the capture of self
, so that a
can be accessed upon applying the closure.
However, in an initialiser, Swift tightly restricts what you can do with self
before it has been fully initialised. One of these restrictions is the inability to be captured by a closure – which is why you get a compiler error.
Although really, there's nothing wrong with the closure { self.a > 0 }
capturing self
before it's fully initialised, because all the closure is doing is accessing a
on it, which has already been initialised. Therefore, this is really just an edge case (for which there's an open bug report), which I hope will be smoothed out in a future version of the language.
Until then, one solution as shown by Martin in this Q&A, is to use a temporary local variable to create a copy of the value of self.a
, avoiding the capturing of self
in the closure:
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 {}
Obviously, this assumes that self.a != a
, otherwise you can just refer to a
instead of self.a
in the guard
condition.