The following codes can build and run successfully. However, the same-type constrain on Self in the extension to ExampleProtocol does not match the return value of the static function and can still be accessed by leading dot syntax. Is that an intentional design in Swift?
protocol ExampleProtocol {
associatedtype T
var value: T { get }
}
struct Example<T>: ExampleProtocol {
let value: T
}
extension ExampleProtocol where Self == Example<Int> { // constraint Self to Example<Int>
static func example<T>(_ value: T) -> Example<T> {
return .init(value: value)
}
}
func test(_ value: any ExampleProtocol) {
print(value.value)
}
test(.example("a")) // used as Example<String>
There is actually two different generic types in this example: associatedtype T and example’s generic T. So you actually call correct extension method, but with its own generic parameter. You need to write func example(_ value: T) so it uses protocol’s type.
I'm aware of that, but I thought when using the leading dot syntax to access static members on a protocol, the return type of the member should match exactly with the constrained type on Self. So in my current understanding, .example("a") here should not be allowed.
Actually I can change the definition of the example function as follow:
Nothing prevents from using dot-syntax on some ExampleProtocol<Int> type with String — that is perfectly fine, since generic type is different. You can think of that as calling Example<Int>.example("a") — it is perfectly sound with constraint.
The constraint disallows writing Example<String>.example(1) for example, since extension is available only for Example<Int>
So in Swift's type system... afaik, Example<Int> and Example<String>are individual types. For you, what are the costs of treating them as one type, and the benefits of changing those semantics?
(I feel like we're hijacking the thread at this point but I'm genuinely curious)