Has it always been possible to refer to `self` in init before and part of assignment of the last stored property?

IIUC it has always worked this way, at least back to Swift 2.x.

The definite initialization pass tracks "uses" of memory locations in a granular way. The initialization state of every "element" of an aggregate is tracked independently, and the pass enforces that uses are only allowed if the relevant set of elements in the use are all known to already be initialized (on all control flow paths that reach that point). This explains the behavior in your examples since, e.g., self.truth is set before it's read to initialize self.snapshot. If it were only conditionally initialized though, it would not be allowed:

struct Holder {
  let snapshot: Snapshot
  let truth: SourceOfTruth
  init() {
    if Bool.random() {
      self.truth = SourceOfTruth()
    }
    self.snapshot = Snapshot(boolSnapshot: self.truth.bool) // 🛑 error: 'self' used before all stored properties are initialized
  }
}

Sort of related – you might find this somewhat recent discussion on how this logic applies to tuples of interest.

I'm not sure computed properties or methods could or should work this way because they both could perform arbitrary accesses of the instance's stored properties, and in general their implementations could be opaque to the compiler, so it must conservatively require full initialization before they can be called.

5 Likes