Autoclosure of self in initializers

I've filed a bug for this here, but I also wanted to consult the Evolution forums since it doesn't seem to be something that is actually wrong with the compiler--the error makes perfect sense as long as you understand @autoclosure.

In brief, the code:

struct Test {
    var prop1: Bool
    var prop2: Bool
    
    init() {
        self.prop2 = false
        self.prop1 = true && self.prop2
    }
}

results in an error on the second line of the initializer:

error: MyPlayground.playground:1:30: error: variable 'self.prop1' captured by a closure before being initialized
        self.prop1 = true && self.prop2
                             ^

because the self.prop2 expression is turned into an autoclosure, forcing the capture of self. Is this the desired behavior? It seems wildly confusing to be unable to use self.prop2 in a logical operator here.

2 Likes

The wording of the error is confusing, but since you normally can't use self before you finish initializing all the properties, I would not be surprised to see an error there. I just think the error should be more helpfully worded.

1 Like

The following workaround works fine:

self.prop2 = false
let temp = self.prop2
self.prop1 = true && temp

so it doesn't seem to be an issue with the usage of self. It should be easy for the compiler to determine which properties of self have definitely been initialized so far.

The issue isn't even necessarily a usage of self before all properties are initialized, because this is totally valid:

struct Test {
  var prop1: Int
  var prop2: Int

  init() {
    self.prop2 = 5
    // The compiler is fine with self.prop2 being used before
    // self.prop1 is initialized
    self.prop1 = 20 - self.prop2
  }
}

So the closure is the point of failure here.

Since && is marked as both @_transparent and @inline(__always), doesn't the compiler have enough knowledge that self isn't "really" being captured in the original post?

1 Like

I think the surprise here might be that && appears not to be a special case in the compiler, but just a normal operator which could be defined by any Swift coder in one of his programs.
Imho that's a good thing, but it would probably be less confusing to have a more vague message like "self used before fully initialized":
It's easy to explain why && tries to avoid evaluation of the second expression, it's easy to explain that this behavior is achieved with a closure - but it would still be an extremely verbose error message...

@Tino Any Swift coder can trivially define the && operator by using the @autoclosure attribute--many Swift programmers are just unaware that it exists.

@allevato Right, it should be pretty apparent to the compiler, because once inlined we'd end up with self.prop1 = false ? try { self.prop2 }() : false which the compiler should be able to figure out needn't capture all of self at all. According to @Joe_Groff on the bug thread, this worked in an older iteration of the compiler when this inlining happened before the definite initialization check, but now that check happens before the inlining occurs.

2 Likes