Home > Software design >  Swift protocols with generic arguments
Swift protocols with generic arguments

Time:10-05

Swift noob here. Consider this Swift 5.7 code:

import Foundation

// This is according to the grammar.
protocol TestProtocol1 {
    associatedtype T
    associatedtype U
}

// Not allowed by the grammer, but still compiles.
protocol TestProtocol2<T> {
    associatedtype T
    associatedtype U
}

// Doesn't seem to matter if I add one or both type arguments.
protocol TestProtocol3<T, U> {
    associatedtype T
    associatedtype U
}

// This is fine. As expected.
class TestClass1 : TestProtocol1 {
    typealias T = Int
    typealias U = Bool
}

// Fine too. Even though I don't specify the type arguments.
class TestClass2 : TestProtocol2 {
    typealias T = Int
    typealias U = Bool
}

// error: cannot inherit from protocol type with generic argument 'TestProtocol3<Int, Bool>'
class TestClass3 : TestProtocol3<Int, Bool> {
    typealias T = Int
    typealias U = Bool
}

Questions:

  • Is there any semantic difference between the three protocol definitions?
  • Why does it compile when declaring the associated types as generic arguments when the grammar doesn't allow it?
  • Why is it useful to (probably redundantly) add type arguments to protocols? For example, protocol Sequence<Element> does this too.

CodePudding user response:

The "generic parameters" that you are seeing are the protocols' primary associated types, proposed in SE-0346, implemented in Swift 5.7, I suppose the grammar section in the language reference just hasn't been updated yet.

The three protocol declarations are semantically different, in that they have different primary associated types. When using the protocol in certain positions, primary associated types are what you can directly specify in <...>, rather than specify them somewhere else like in a where clause. For example, when using the protocol as a generic constraint:

func foo<P: TestProtocol2<Int>>(p: P) { ... }

is syntactic sugar for:

func foo<P: TestProtocol2>(p: P) where P.T == Int { ... }

The former is just a little more concise :)

You cannot do something similar with TestProtocol1, because it doesn't have primary associated types.

For TestProtocol3, you must specify both primary associated types:

func foo<P: TestProtocol3<Int, Bool>>(p: P) { ... }

According to the SE proposal, this syntax was also planned to be usable in the protocol conformance clause of a concrete type, like in your code:

class TestClass3 : TestProtocol3<Int, Bool> { // does not compile

However, this feature did not get added for some reason. You can still use it in the inheritance clause of a protocol though:

protocol TestProtocol4: TestProtocol3<Int, Bool> { } // works

See the SE proposal for more details.

CodePudding user response:

For the first question Is there any semantic difference between the three protocol definitions?

I dont think so . When you create a protocol with <..> after protocol name , Protocols think that the name giving between <> is a associated type or types but you must add that associatedtype name with the same in <>

For example if you delete U TestProtocol3 like

protocol TestProtocol3<T, U>{
    associatedtype T
}

you will get an error says : An associated type named 'U' must be declared in the protocol 'TestProtocol3' or a protocol it inherits.In the opposite way if you delete U in protocol TestProtocol3<T, U> like protocol TestProtocol3<T> , will not give an any error.

CodePudding user response:

Here is my answer to your questions:

Is there any semantic difference between the three protocol definitions?

The only difference it's that TestProtocol1 has the correct declaration.

Why does it compile when declaring the associated types as generic arguments when the grammar doesn't allow it?

I made the test and it doesn't compile for me! kind weird

  • Related