Hello!
I'm experiencing a strange and unwanted behaviour with Protocols and Extensions that I'm hoping someone here can both explain why this is happening, and what I should do to prevent it.
Here's a boiled-down example:
protocol Fruit {
var colour: String { get }
var price: Double { get }
}
struct Apple: Fruit {
var colour = "red"
var price: String = "Expensive"
}
The compiler complains about the type mismatch on price
, here. This is expected.
My code, howeever, actually had an extension on the analog to the
Fruit
protocol here, to provide a default [computed (which is necessary in an extension)] value. Like this:
protocol Fruit {
var colour: String { get }
var price: Double { get }
}
extension Fruit {
var price: Double { 3.99 }
}
This also works fine. If I follow up with definition of Apple, I get:
struct Apple: Fruit {
var colour = "red"
}
let apple = Apple()
apple.price // 3.99 (double)
The price
is correct, here: 3.99
I can however, overwrite (sort of, more on this below) the type by declaring it in the struct.
struct Apple: Fruit {
var colour = "red"
var price = "expensive"
}
let apple = Apple()
apple.price // "expensive" (string)
This only works if I declare the property in the extension. If I do, I can change the type in the struct.
Even more suspicious, if I do the above, I can get two different price properties:
let apple = Apple()
let stringPrice: String = apple.price // "expensive" (string)
let numPrice: Double = apple.price // 3.99 (double)
This gets especially messy when trying to call functions with the same name but different signatures:
func doStuff(_ arg: String) {
print("String doStuff!")
}
func doStuff(_ arg: Double) {
print("Double doStuff!")
}
doStuff(apple.price) // which one is called?
It's the String one that gets called in my testing, but I expected the definition in the protocol to enforce doStuff(Double)
.
(Note: I discovered this while refactoring what is price
in this example from one type to another; it was a much bigger struct and the price
declaration was missed; the compiler didn't complain so it was ambiguous to me until I realized that it was using the old type for price
. The example is contrived, but this is a thing I actually experienced.)
So, here's my main question: is this expected behaviour?
Secondarily: what can I do to force the type in the protocol? (see the note about force-unwrapped-type below)
Here are the parts that seem strange to me:
- the protocol doesn't actually enforce the type if the extension declares
price
- the extension isn't allowed to declare a different type
- there can be two properties with the same name, but I can't declare two properties of the same name (and different types) in a normal
struct
definition - there's no obvious way to ensure the type in the protocol (properties can't be marked
final
for example) - if, I mark the type
Double!
in the extension (this was an accidental discovery), the compiler does complain about the type change in the struct; I don't think this is the correct way to do this, though, and it's probably just a side effect of forcing the unwrap. https://files.scoat.es/4Mlwoo3dNM.png - if I define a type in a protocol, and something else extends the protocol in this way,
apple.price as! Double
becomes a crash (the fact that I should not be force casting there aside)
Thanks for reading this far. I know it's a long post but I wanted to be as clear as possible about what seems like a really strange behaviour (and maybe even a compiler misbehaviour) to me.
S