This surprised me… a lot. So much that I’ve come to question my identify as a seasoned Swift developer… so I’m curious, has this ALWAYS worked, just that I’ve missed it? Or has a change happened along the way (since 2014)?
final class MyViewModel {
var boolProxy: Bool
init(boolProxy: Bool) {
self.boolProxy = boolProxy
}
}
final class MyView: NSObject {
var bool = true
}
final class VC: NSObject {
let view: MyView
let vm: MyViewModel
override init() {
self.view = MyView()
// Today I learned that I can use `self.` on row below, even though `self.vm` is not
// set "to the right of `=`".
self.vm = MyViewModel(boolProxy: self.view.bool)
super.init()
}
}
Or more generally:
struct Snapshot {
let boolSnapshot: Bool
init(boolSnapshot: Bool) {
self.boolSnapshot = boolSnapshot
}
}
struct SourceOfTruth {
var bool = true
}
struct Holder {
let snapshot: Snapshot
let truth: SourceOfTruth
init() {
self.truth = SourceOfTruth()
// Today I learned that I can use `self.` on row below, even though `self.vm` is not
// set "to the right of `=`".
self.snapshot = Snapshot(boolSnapshot: self.truth.bool)
}
}
I would have thought we could not reference self part of setting the last stored property, since until set “self” cannot be referenced (was my incorrect belief).
But you are not using self.. you are using self.truth etc - that was already initialised.
If you were to use self (e.g. print(self)) that would be an error.
I think it was always like this.
I asked the AI overlords when we were able to stop using redundant local variables for what you demonstrated, and it could not give me a definitive answer.
Stored properties work that way. Instance computed properties, like methods, do not. It seems to me that they should.
struct Instance {
var computed: Void { }
let stored: [Void]
init() {
computed // ❌ 'self' used before all stored properties are initialized
stored = []
computed
}
}
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
}
}
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.
I wanted to emphasize that link that @jamieQ posted is to swiftfiddle.com, which is a very useful online playground for Swift. It allows you to try versions of Swift going back to Swift 2.2, the first open source version.
This is a great resource for answering questions like “Has this always worked this way?” Or “When did this start working this way?”
Agree. We probably should bring these, at least the major ones, to Godbolt as well. @jamieQ, I think you were the one adding the most recent versions to Godbolt. Do you see any reason why we can’t do this with older versions that haven’t been added yet?
I don't see why not. The primary barrier for me personally doing this the last time I thought about it was divining what the right URLs are to get the Ubuntu artifacts. Luckily, the LLMs have now helped figure that out. I'm not sure since they're associated with such an old version of Ubuntu if there will be issues running them on the compiler explorer hosts, but I guess we'll find out. Opened some draft PRs: 1 & 2.
Right, I've always thought about self.property as using self, in the particular case I’ve always went through the init-dance of a let instanceOfClass and later self.instanceOfClass = instanceOfClass - entirely meaningless!
No, not really sure other than the superficial explanation that it's explicitly disabled in the config. Execution support seems to have been added here and one of the commits says that those disabled older releases had runtime errors.