I have a question about opaque results types that I'm hoping someone can help me with. There's something I don't understand. My question is perhaps best explained in code:
protocol Bar {}
struct Foo: Bar {}
func test(foo: Foo) {
print("test(foo:Foo)")
}
func test<B: Bar>(foo: B) {
print("test(foo:B)")
}
func fooGenerator() -> some Bar { Foo() }
// Calls test(foo: B)
test(foo: fooGenerator())
// Calls test(foo: Foo)
test(foo: Foo())
I had expected that both function calls above would result in test(foo: Foo) being called. Could anyone explain why this is not the case? Is there a way, while still using Opaque result types, to have the behaviour I'm expecting? I thought the underlying type was preserved.
Thanks in advance for any help, and increasing my understanding here!
It's a tricky thing. Let's give some definitions I'll be using first:
static type of something is the type known at the compile type. You can check that by hovering over the symbol in sourcekit-lsp, or alt-clicking in Xcode.
dynamic type of something is the type of the object in memory, known only at runtime. You can check that one with type(of:) function
Languages like python don't have the static types, so you have only one function with the given name at the time, and methods are always dispatched dynamically.
some preserves the dynamic type, but erases the static type. When you alt-click fooGenerator you will see that it returns some Bar, not Foo. At compile time there's no information that it was Foo that was returned
Function calls are dispatched according to the static type, which isn't what you want. There are a few solutions you can use:
Preserve the static type information all the way through
probably not what you want, but just writing for the sake of completeness
protocol Bar {}
struct Foo: Bar {}
func test(foo: Foo) {
print("test(foo:Foo)")
}
func test<B: Bar>(foo: B) {
print("test(foo:B)")
}
func fooGenerator() -> Foo { Foo() } // I changed the return type here
// Calls test(foo: Foo)
test(foo: fooGenerator())
// Calls test(foo: Foo)
test(foo: Foo())
restore the static type with casting
protocol Bar {}
struct Foo: Bar {}
func test(foo: Foo) {
print("test(foo:Foo)")
}
func test<B: Bar>(foo: B) {
if let foo = foo as? Foo {
test(foo: foo)
} else {
print("test(foo:B)")
}
}
func fooGenerator() -> some Bar { Foo() }
// Calls test(foo: B) which calls test(foo: Foo)
test(foo: fooGenerator())
// Calls test(foo: Foo)
test(foo: Foo())
use protocol
methods defined in protocol are dispatched using the dynamic type, not the static type. The problem with that is that you cannot add methods to protocols defined by someone else
protocol Bar {
func test()
}
extension Bar {
func test() {
print("different Bar")
}
}
struct Foo: Bar {
func test() {
print("Foo")
}
}
func fooGenerator() -> some Bar { Foo() }
// prints Foo
fooGenerator().test()
// prints Foo
Foo().test()
I like the protocols approach, but I want the syntax from the first post
protocol Bar {
func _test()
}
extension Bar {
func _test() {
print("different Bar")
}
}
struct Foo: Bar {
func _test() {
print("Foo")
}
}
func test(foo: Bar) {
foo._test()
}
func fooGenerator() -> some Bar { Foo() }
// prints Foo
test(foo: fooGenerator())
// prints Foo
test(foo: Foo())