6

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?

Rengers
  • 14,911
  • 1
  • 36
  • 54
Kamil Nomtek.com
  • 1,590
  • 1
  • 12
  • 18
  • `[a|b]ExpectedValue` are instance properties, i.e. linked to the `self`, and you can't use `self` in `init` until you initialise it properly. – user28434'mstep Apr 30 '19 at 13:56
  • @user28434 then why it works with `c = (a == aExpectedValue)` only? – Kamil Nomtek.com Apr 30 '19 at 14:02
  • 1
    Related: [What caused 'Constant captured by a closure before being initialized' error](https://stackoverflow.com/questions/38291580/what-caused-constant-captured-by-a-closure-before-being-initialized-error) and [Swift error using initialized properties in expressions before super.init](https://stackoverflow.com/questions/27302281/swift-error-using-initialized-properties-in-expressions-before-super-init) – Martin R Apr 30 '19 at 14:08

2 Answers2

15

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)
}
Rob Napier
  • 286,113
  • 34
  • 456
  • 610
0

You need to create a new initializer with another vars or call super.init(a:, b:) before any expression with this properties.

Call this:

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

or change it to:

init(newA: String, newB: String) {
    c = (newA == aExpectedValue && newB == bExpectedValue)        
    super.init(a: newA, b: newB)
}
Agisight
  • 1,778
  • 1
  • 14
  • 15