-
Notifications
You must be signed in to change notification settings - Fork 10.5k
[Docs] Rework educational note on protocol type non-conformance #33735
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,46 +1,61 @@ | ||
# Protocol type not conforming to itself | ||
Protocols in Swift may be used as types. Protocols as types are sometimes called existential types. | ||
# Protocol Types Cannot Conform to Protocols | ||
|
||
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: | ||
|
||
```swift | ||
protocol P {} | ||
|
||
struct S: P {} | ||
protocol Animal { | ||
func makeNoise() | ||
static var species: String { get } | ||
} | ||
struct Dog: Animal { | ||
func makeNoise() { print("Woof") } | ||
static var species: String = "Canus familiaris" | ||
} | ||
struct Cat: Animal { | ||
func makeNoise() { print("Meow") } | ||
static var species: String = "Felis catus" | ||
} | ||
|
||
var s: P = S() // This creates existential type because the protocol P is used as a type | ||
var animal: Animal // `Animal` is used here as a type. | ||
animal = Dog() | ||
animal.makeNoise() // Prints "Woof". | ||
animal = Cat() | ||
animal.makeNoise() // Prints "Meow". | ||
``` | ||
|
||
However, a protocol type does not conform to protocols - not even the protocol itself. | ||
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. | ||
|
||
Let's walk through the example below: | ||
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: | ||
|
||
```swift | ||
protocol Word: Hashable { | ||
var word: String { get } | ||
} | ||
print(Dog.species) // Prints "Canus familiaris" | ||
print(Cat.species) // Prints "Felis catus" | ||
print(Animal.species) // error: static member 'species' cannot be used... | ||
``` | ||
|
||
struct Singular: Word { | ||
var word: String | ||
} | ||
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`: | ||
|
||
struct Plural: Word { | ||
var word: String | ||
```swift | ||
func declareAnimalSpecies<T: Animal>(_ animal: T) { | ||
animal.makeNoise() | ||
print("My species is known as \(T.species)") | ||
} | ||
|
||
let singularWord = Singular(word: "mango") | ||
let pluralWord = Plural(word: "mangoes") | ||
|
||
let wordPairDict: [Word: Word] = [singularWord: pluralWord] // Error | ||
let dog = Dog() | ||
declareAnimalSpecies(dog) | ||
// Prints: | ||
// "Woof" | ||
// "My species is known as Canus familiaris" | ||
declareAnimalSpecies(animal) | ||
// error: protocol type 'Animal' cannot conform to 'Animal'... | ||
``` | ||
|
||
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. | ||
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. | ||
|
||
```swift | ||
// The fix | ||
let wordPairDict: [AnyHashable: AnyHashable] = [singularWord: pluralWord] | ||
``` | ||
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. | ||
|
||
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. | ||
|
||
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_. | ||
|
||
# Exceptions | ||
`@objc` protocol type with no static requirements however do conform to its own protocol. Another exception is the `Error` Swift protocol. | ||
## Exceptions | ||
|
||
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. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Two other notes about self-conforming protocols that may be relevant:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @tbkka Thanks! Regarding (1), are the bugs sufficiently rare that we can tell users that this relationship is true of today's Swift? Regarding (2), can you help outline what you're referring to in terms of its applicability to generic types? Besides manual type erasure, what other workarounds are you thinking of in that case? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Because of how Swift handles protocols and generics, the latter is essentially the same but avoids the self-conformance problem. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it should be “contravariant Self”, because this works:
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The details are more applicable to the other educational note on PATs. However, I'm a bit surprised that this works, since
var foo: Self { get }
does not.Edit: I've opened a draft update of the other docs, where I think the details are most salient for what you point out. Still not sure why the discrepancy between
func
andvar
here though...There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure if I'm thinking on the right track, just a thought to throw about:
One of the differences between functions and computed variables is that only functions can throw errors.
Error
is the only protocol in Swift that has self-conformance. Is there any special handling added to functions because ofError
, that might explain the discrepancy?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not just
Error
.Any
and many Obj-C protocols are also self-conformant.