andyhill
(Andy Hill)
1
On Xcode14beta5, macOS Ventura 13.0, the following code runs fine:
protocol P { var p: String { get } }
struct S: P { let p="hello world" }
protocol Q {
associatedtype A
func f(a: A)
}
struct R: Q {
typealias A = S
func f(a: S) { print(a.p) }
}
let r = R()
r.f(a: S()) // Output: hello world
Reading SE-0346 section "Other positions", I thought I should be able to use the new constrained protocol syntax with the primary associated type A as in:
protocol Q<A> {
associatedtype A
func f(a: A)
}
struct R: Q<S> {
//typealias A = S
func f(a: S) { print(a.p) }
}
but Xcode gave me the error message, "Cannot inherit from protocol type with generic argument 'Q<S>'" on the struct R: Q<S> line. Am I using the new syntax incorrectly? What's the correct usage? Thanks.
2 Likes
My apologies, this is not currently implemented because the behavior of a constrained protocol type here is slightly different than the other positions; as you guessed, it implies a type alias. We should make it work, so that
struct R: Q<S> {}
is equivalent to the second form with the commented out type alias:
struct R: Q {
typealias A = S
}
In the meantime, you can write the second form.
3 Likes
andyhill
(Andy Hill)
3
Cool. Thanks for the clarification.
pyrtsa
(Pyry Jahkola)
4
@Slava_Pestov Is this missing support tracked as an issue in GitHub currently?
3 Likes
@Slava_Pestov is this implemented in Swift 5.9? I think this makes it much easier to translate conformances to dependency injection, e.g.
struct R: Q<S> {}
is much easier to copy/paste as a generic constraint to a function:
function doThing(_ thing: some Q<S>)
1 Like
bobbel
6
@Slava_Pestov
Is there a timeline when this might be implemented?
It would safe me a lot of boilerplate if I could write something like this:
protocol ActionInterface {
associatedtype ReturnType
}
protocol ActionExecutor<Action> {
associatedtype Action: ActionInterface
func execute(action: Action) throws -> Action.ReturnType
}
struct MyStringAction: ActionInterface {
typealias ReturnType = String
}
struct MyIntAction: ActionInterface {
typealias ReturnType = Int
}
struct Client: ActionExecutor<MyStringAction>, ActionExecutor<MyIntAction> {
func execute(action: MyStringAction) throws -> String {
"foo"
}
func execute(action: MyIntAction) throws -> Int {
123
}
}
Right now there is no way to add multiple implementations of the same protocol but with different associated types.
The feature you’re asking for is syntax sugar for declaring a conformance together with a type alias so it doesn’t really permit anything new. The Swift generics model does not permit a concrete type to conform to the same protocol more than once.
bobbel
8
But is it really the same protocol if it has a different associated type? In my example all the underlying meta types are different.
Imagine my Client needs to implement 20 different Actions.
If it's considered syntactic sugar, what's the current 'desugared' syntax?
Suppose I write this:
protocol P { associatedtype A }
struct S {}
extension S: P { typealias A = Int }
extension S: P { typealias A = String }
func f<T: P>(_: T) { print(T.A.self) }
f(S())
If this were allowed, would this print Int or String?
1 Like
bobbel
10
Sorry, I think I misunderstood your previous message. You were explaining that lifting this limitation wouldn't enable my desired use-case and not that my use-case is syntactic sugar.
But wouldn't this be a prerequisite for a hypothetical dynamic typealias?
protocol P<A> { associatedtype A }
struct S: P { associatedtype A = Double }
extension S: P<Int> // interpreted { typealias P.Int = Int }
extension S: P<String> // interpreted { typealias P.String = String }
This way print would be well defined.
But I think the crucial question is whether a protocol with an associated type should be considered unique.
xwu
(Xiaodi Wu)
11
When a struct S conforms to a protocol P, it may do so in only one way; that is, it may and must only fulfill each requirement exactly once. This includes the associated type requirement A.
Protocols are not generic, and primary associated types are not generic parameters. In fact, one reason we adopted P<A> syntax for a primary associated type A is precisely because we have no plan to support a generic parameter A using the same syntax.
1 Like