the curious case of nil-coalescing operator
Using ??
within a class initializer under some circumstances leads to an ambiguous compiler error message.
I encountered this problem when I was writing a class like this:
class MyClass {
private var fname: String?
private var lname: String
private var displayName: String
init(firstName: String?, lastName: String) {
self.fname = firstName
self.lname = lastName
displayName = self.fname ?? self.lname // Compiler-Error: 'self' captured by a closure before all members were initialized
}
}
The compiler error message looks odd. There isn’t any closure there!
Rewriting that problematic line in another way without using ??
operator, for example:
displayName = self.fname != nil ? self.fname! : self.lname
resolves the issue. Also, replacing self.lname
with anything that is not a property of self
, resolves the issue too. So, apparently, the issue has something do to with self
and ??
operator.
After looking at the implementation of nil-coalescing operator in Optional.swift, you will know why this is so:
public func ?? <T>(optional: T?, defaultValue: @autoclosure () throws -> T?)
rethrows -> T? {
switch optional {
case .some(let value):
return value
case .none:
return try defaultValue()
}
}
The second parameter of this operator is a @autoclosure
. It means it is a closure even though we don’t want :)
So this is how the compiler sees that line:
displayName = self.fname ?? (){ self.lname }
Because the value of displayName
is not known unless the expression at the right-hand side of =
is evaluated and the expressed doesn’t get evaluated unless the right-hand side of ??
(the auto-closure) is executed. But this closure captures self
. So we are capturing self
while self
is not fully initialized. Now we can understand what the error message means:
'self' captured by a closure before all members were initialized
Do you have question, suggestion, feedback? Hit me on twitter: @coybit🙂