Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Strange "'self' captured by a closure before all members were initialized" error

Tags:

swift

Please take a look at the following code:

class A {
    let a: String
    let b: String

    init(a: String, b: String) {
        self.a = a
        self.b = b
    }
}

class B: A {
    let c: Bool

    private let aExpectedValue = "a"
    private let bExpectedValue = "b"

    override init(a: String, b: String) {
        c = (a == aExpectedValue && b == bExpectedValue)
        super.init(a: a, b: b)
    }
}

This causes an error in B.init:

error

However, if I change it either to c = (a == aExpectedValue) or c = (b == bExpectedValue) then it compiles correctly.

Does anybody know why is that?

like image 934
Kamil Nomtek.com Avatar asked Apr 30 '19 13:04

Kamil Nomtek.com


1 Answers

The problem is in bExpectedValue. That's an instance property on B. That interacts with the definition of && on Bool:

static func && (lhs: Bool, rhs: @autoclosure () throws -> Bool) rethrows -> Bool

The @autoclosure makes the b == bExpectedValue into a closure, capturing it as self.bExpectedValue. That's not allowed before initialization is complete. (The closure here is to allow short-circuiting. The rhs closure is not evaluated if lhs is false.)

This is pretty awkward (see SR-944 that MartinR references for a little discussion about it).

If bExpectedValue were static, or if it were moved outside the class definition, then this wouldn't be an issue. The following approach will also fix it:

override init(a: String, b: String) {
    let goodA = a == aExpectedValue
    let goodB = b == bExpectedValue
    c = goodA && goodB
    super.init(a: a, b: b)
}
like image 81
Rob Napier Avatar answered Nov 05 '22 15:11

Rob Napier