laclouis5
(Lac Louis)
1
Consider I have this abstract base class with one virtual method with a return type of Foo:
class Foo {
func foo() -> Foo { fatalError("abstract") }
}
Because Foo is in covariant position, a subclass can return an instance of a subclass of Foo, like this:
final class SubFoo: Foo {
let value: Int = 0
override func foo() -> SubFoo {
return SubFoo()
}
}
This is convenient because when a given instance is statically known to be of type SubFoo, then .foo() will return an instance of type SubFoo. At the opposite, when an instance of a subclass of Foo is erased to Foo, then .foo() returns a type-erases Foo:
let subFoo = SubFoo().foo() // Statically known to be of type `SubFoo`
let foo = (SubFoo() as Foo).foo() // `foo` is erased to `Foo`
My issue is that I cannot find a way to replicate this nice behavior with protocols and existentials:
protocol Bar {
func bar() -> any Bar
}
struct ImplBar: Bar {
var value: Int = 0
func bar() -> ImplBar { // Error: returning `ImplBar` not allowed
return ImplBar()
}
}
Is there a way to make the protocol version to work or this behavior is not supported by Swift protocols/existentials?
protocol Bar {
func bar() -> Self
}
Maybe not quite as ergonomic as you'd want (see pitfalls), but I think it does the job?
xAlien95
(Stefano De Carolis)
3
Have you considered adding an associated type to Foo as a protocol? In Swift 5.7 you should get the same behavior you got via subclassing:
protocol Foo {
associatedtype Return: Foo
func foo() -> Return
}
struct SubFoo: Foo {
let value = 0
func foo() -> SubFoo {
return SubFoo()
}
}
let subFoo = SubFoo().foo() // Statically known to be of type `SubFoo`
let foo = (SubFoo() as any Foo).foo() // `foo` is erased to `any Foo`
2 Likes
laclouis5
(Lac Louis)
4
This would work in this specific case, yes, but it does not generalize unfortunately:
protocol Baz { }
struct ImplBaz: Baz { }
protocol Bar {
func bar() -> any Baz
}
struct ImplBar: Bar {
var value: Int = 0
// Not possible to return a concrete type instead of a type-erased `Baz`
func bar() -> ImplBaz {
return ImplBaz()
}
}
1 Like
laclouis5
(Lac Louis)
5
I just tried it using Swift 5.7 and I think it works as intended!