Skip to content

Commit 75b1a7e

Browse files
authored
Merge pull request #33735 from xwu/existential-edu
2 parents f4fcb40 + e34a065 commit 75b1a7e

File tree

1 file changed

+44
-29
lines changed

1 file changed

+44
-29
lines changed
Lines changed: 44 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,61 @@
1-
# Protocol type not conforming to itself
2-
Protocols in Swift may be used as types. Protocols as types are sometimes called existential types.
1+
# Protocol Types Cannot Conform to Protocols
32

3+
In Swift, a protocol that does not have `Self` or associated type requirements can be used as a type. You can use a variable or constant of a protocol type, also called an __existential type__, to hold a value of any conforming type:
44

55
```swift
6-
protocol P {}
7-
8-
struct S: P {}
6+
protocol Animal {
7+
func makeNoise()
8+
static var species: String { get }
9+
}
10+
struct Dog: Animal {
11+
func makeNoise() { print("Woof") }
12+
static var species: String = "Canus familiaris"
13+
}
14+
struct Cat: Animal {
15+
func makeNoise() { print("Meow") }
16+
static var species: String = "Felis catus"
17+
}
918

10-
var s: P = S() // This creates existential type because the protocol P is used as a type
19+
var animal: Animal // `Animal` is used here as a type.
20+
animal = Dog()
21+
animal.makeNoise() // Prints "Woof".
22+
animal = Cat()
23+
animal.makeNoise() // Prints "Meow".
1124
```
1225

13-
However, a protocol type does not conform to protocols - not even the protocol itself.
14-
Allowing existential types to conform to protocols is unsound. For protocols with static method, initializer, or associated type requirements, the implementation of these requirements cannot be accessed from the protocol type - accessing these kinds of requirements must be done using a concrete type.
15-
16-
Let's walk through the example below:
26+
Notice that it is possible to invoke the method `makeNoise()` on a value of type `Animal`, just as it is possible to do so on a value of concrete type `Dog` or `Cat`. However, the static property `species` is not available for the existential type:
1727

1828
```swift
19-
protocol Word: Hashable {
20-
var word: String { get }
21-
}
29+
print(Dog.species) // Prints "Canus familiaris"
30+
print(Cat.species) // Prints "Felis catus"
31+
print(Animal.species) // error: static member 'species' cannot be used...
32+
```
2233

23-
struct Singular: Word {
24-
var word: String
25-
}
34+
Since a type conforms to a protocol only when it satisfies _all_ of that protocol's requirements, the existential type `Animal` does not conform to the protocol `Animal` because it cannot satisfy the protocol's requirement for the static property `species`:
2635

27-
struct Plural: Word {
28-
var word: String
36+
```swift
37+
func declareAnimalSpecies<T: Animal>(_ animal: T) {
38+
animal.makeNoise()
39+
print("My species is known as \(T.species)")
2940
}
3041

31-
let singularWord = Singular(word: "mango")
32-
let pluralWord = Plural(word: "mangoes")
33-
34-
let wordPairDict: [Word: Word] = [singularWord: pluralWord] // Error
42+
let dog = Dog()
43+
declareAnimalSpecies(dog)
44+
// Prints:
45+
// "Woof"
46+
// "My species is known as Canus familiaris"
47+
declareAnimalSpecies(animal)
48+
// error: protocol type 'Animal' cannot conform to 'Animal'...
3549
```
3650

37-
One workaround to fix this problem is to use type erasure for the protocol `Word`. Think of type erasure as a way to hide an object's type. Since `Word` is of type `Hashable`, we already have `AnyHashable` type erasure available in the standard library which we can easily use here.
51+
In general, any initializers, static members, and associated types required by a protocol can be used only via conforming concrete types. Although Swift allows a protocol that requires initializers or static members to be used as a type, that type _does not and cannot_ conform to the protocol itself.
3852

39-
```swift
40-
// The fix
41-
let wordPairDict: [AnyHashable: AnyHashable] = [singularWord: pluralWord]
42-
```
53+
Currently, even if a protocol `P` requires no initializers or static members, the existential type `P` does not conform to `P` (with exceptions below). This restriction allows library authors to add such requirements (initializers or static members) to an existing protocol without breaking their users' source code.
54+
55+
Concrete types that _do_ conform to protocols can provide functionality similar to that of existential types. For example, the standard library provides the `AnyHashable` type for `Hashable` values. Manual implementation of such __type erasure__ can require specific knowledge of the semantic requirements for each protocol involved and is beyond the scope of this discussion.
56+
57+
For more on using existential types, see [Protocols as Types](https://docs.swift.org/swift-book/LanguageGuide/Protocols.html#ID275) in _The Swift Programming Language_.
4358

44-
# Exceptions
45-
`@objc` protocol type with no static requirements however do conform to its own protocol. Another exception is the `Error` Swift protocol.
59+
## Exceptions
4660

61+
The Swift protocol `Error` has no required members and, when used as a type, conforms to itself; `@objc` protocols with no static requirements can also be used as types that conform to themselves.

0 commit comments

Comments
 (0)