Reputation: 21
I'm running into a weird runtime error when I applied the Self constraint on the protocol itself, while the code ran as expected when I changed the constraint onto an extension of the protocol. I wonder if anyone can help me explain the difference, or help me confirm that this is unexpected behavior (i.e. a language bug). Here is the code that does not work (it's written in Playground, so the main logic is not in a method):
class Foo {
let val: Int
init(_ val: Int) {
self.val = val
}
}
protocol IFooComparer where Self: Foo {
func compareTo(other: Foo) -> Bool?
}
class Bar : Foo, IFooComparer {
func compareTo(other: Foo) -> Bool? {
guard let otherBar = other as? Bar else { return nil }
return self.val < otherBar.val
}
}
let foos: [Foo] = [
Foo(1),
Bar(2),
]
let newFoo = Bar(3)
for foo in foos {
if let comparer = foo as? IFooComparer, let result = comparer.compareTo(other: newFoo) {
print("\(foo.val): \(result)")
} else {
print("\(foo.val): Cannot compare")
}
}
The code will succeed on the first loop iteration, and then fail on the second loop iteration, where it throws EXC_BAD_ACCESS. From what I can gather in debugger, the scoped variable "comparer" created inside the loop body seems to be a valid object before the call to compareTo(); but inside the compareTo(), the "self" variable seems like a corrupted/deallocated object, which causes the exception. Now the weird part is that if I change the protocol definition to:
protocol IFooComparer {
func compareTo(other: Foo) -> Bool?
}
extension IFooComparer where Self: Foo { }
The code will run fine. Can someone help me explain the differences? Thanks! P.S: I'm running Xcode Version 9.2 (9C40b)
Upvotes: 2
Views: 890
Reputation: 704
The approach that "works" isn't actually doing the same thing. It doesn't need to resolve the constraint anymore, since the method is declared in the protocol, not the extension.
Using this logic, let's add a method to that extension and try to see if this way it can resolve the constraint.
So we have this working approach:
protocol IFooComparer {
func compareTo(other: Foo) -> Bool?
}
extension IFooComparer where Self: Foo { }
Let's have the extension add a new method, compare2
extension IFooComparer where Self: Foo {
func compareTo2(other: Foo) -> Bool? {
return nil
}
}
Now, if we were to use this method instead of the regular compare
, we would have
if let comparer = foo as? IFooComparer,
let result = comparer.compareTo2(other: newFoo)
This also doesn't work, because
'IFooComparer' is not a subtype of 'Foo'
This tells me the compiler has an issue resolving the constraint from the extension.
The same happens when you specify the constraint directly to the protocol itself.
One solution to this is to cast to the concrete class that implements the protocol, i.e. Bar
:
protocol IFooComparer where Self: Foo {
func compareTo(other: Foo) -> Bool?
}
for foo in foos {
if let c = foo as? Bar, let result = c.compareTo(other: foo) {
...
Based on this, I think the problem is in some cases the compiler can't verify the protocol/extension constraint. It doesn't have to do with the fact that you set a constraint on the protocol itself.
I would love to hear a precise explanation for this though, I'm not sure why it can't resolve the constraint.
Upvotes: 1