2058 lines
63 KiB
Markdown
2058 lines
63 KiB
Markdown
# Generics
|
|
|
|
Write code that works for multiple types and specify requirements for those types.
|
|
|
|
*Generic code* enables you to write flexible, reusable functions and types
|
|
that can work with any type, subject to requirements that you define.
|
|
You can write code that avoids duplication
|
|
and expresses its intent in a clear, abstracted manner.
|
|
|
|
Generics are one of the most powerful features of Swift,
|
|
and much of the Swift standard library is built with generic code.
|
|
In fact, you've been using generics throughout the *Language Guide*,
|
|
even if you didn't realize it.
|
|
For example, Swift's `Array` and `Dictionary` types
|
|
are both generic collections.
|
|
You can create an array that holds `Int` values,
|
|
or an array that holds `String` values,
|
|
or indeed an array for any other type that can be created in Swift.
|
|
Similarly, you can create a dictionary to store values of any specified type,
|
|
and there are no limitations on what that type can be.
|
|
|
|
## The Problem that Generics Solve
|
|
|
|
Here's a standard, nongeneric function called `swapTwoInts(_:_:)`,
|
|
which swaps two `Int` values:
|
|
|
|
```swift
|
|
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
|
|
let temporaryA = a
|
|
a = b
|
|
b = temporaryA
|
|
}
|
|
```
|
|
|
|
<!--
|
|
- test: `whyGenerics`
|
|
|
|
```swifttest
|
|
-> func swapTwoInts(_ a: inout Int, _ b: inout Int) {
|
|
let temporaryA = a
|
|
a = b
|
|
b = temporaryA
|
|
}
|
|
```
|
|
-->
|
|
|
|
This function makes use of in-out parameters to swap the values of `a` and `b`,
|
|
as described in <doc:Functions#In-Out-Parameters>.
|
|
|
|
The `swapTwoInts(_:_:)` function swaps the original value of `b` into `a`,
|
|
and the original value of `a` into `b`.
|
|
You can call this function to swap the values in two `Int` variables:
|
|
|
|
```swift
|
|
var someInt = 3
|
|
var anotherInt = 107
|
|
swapTwoInts(&someInt, &anotherInt)
|
|
print("someInt is now \(someInt), and anotherInt is now \(anotherInt)")
|
|
// Prints "someInt is now 107, and anotherInt is now 3".
|
|
```
|
|
|
|
<!--
|
|
- test: `whyGenerics`
|
|
|
|
```swifttest
|
|
-> var someInt = 3
|
|
-> var anotherInt = 107
|
|
-> swapTwoInts(&someInt, &anotherInt)
|
|
-> print("someInt is now \(someInt), and anotherInt is now \(anotherInt)")
|
|
<- someInt is now 107, and anotherInt is now 3
|
|
```
|
|
-->
|
|
|
|
The `swapTwoInts(_:_:)` function is useful, but it can only be used with `Int` values.
|
|
If you want to swap two `String` values,
|
|
or two `Double` values,
|
|
you have to write more functions,
|
|
such as the `swapTwoStrings(_:_:)` and `swapTwoDoubles(_:_:)` functions shown below:
|
|
|
|
```swift
|
|
func swapTwoStrings(_ a: inout String, _ b: inout String) {
|
|
let temporaryA = a
|
|
a = b
|
|
b = temporaryA
|
|
}
|
|
|
|
func swapTwoDoubles(_ a: inout Double, _ b: inout Double) {
|
|
let temporaryA = a
|
|
a = b
|
|
b = temporaryA
|
|
}
|
|
```
|
|
|
|
<!--
|
|
- test: `whyGenerics`
|
|
|
|
```swifttest
|
|
-> func swapTwoStrings(_ a: inout String, _ b: inout String) {
|
|
let temporaryA = a
|
|
a = b
|
|
b = temporaryA
|
|
}
|
|
|
|
-> func swapTwoDoubles(_ a: inout Double, _ b: inout Double) {
|
|
let temporaryA = a
|
|
a = b
|
|
b = temporaryA
|
|
}
|
|
```
|
|
-->
|
|
|
|
You may have noticed that the bodies of
|
|
the `swapTwoInts(_:_:)`, `swapTwoStrings(_:_:)`, and `swapTwoDoubles(_:_:)` functions are identical.
|
|
The only difference is the type of the values that they accept
|
|
(`Int`, `String`, and `Double`).
|
|
|
|
It's more useful, and considerably more flexible,
|
|
to write a single function that swaps two values of *any* type.
|
|
Generic code enables you to write such a function.
|
|
(A generic version of these functions is defined below.)
|
|
|
|
> Note: In all three functions,
|
|
> the types of `a` and `b` must be the same.
|
|
> If `a` and `b` aren't of the same type,
|
|
> it isn't possible to swap their values.
|
|
> Swift is a type-safe language,
|
|
> and doesn't allow (for example) a variable of type `String`
|
|
> and a variable of type `Double`
|
|
> to swap values with each other.
|
|
> Attempting to do so results in a compile-time error.
|
|
|
|
## Generic Functions
|
|
|
|
*Generic functions* can work with any type.
|
|
Here's a generic version of the `swapTwoInts(_:_:)` function from above,
|
|
called `swapTwoValues(_:_:)`:
|
|
|
|
```swift
|
|
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
|
|
let temporaryA = a
|
|
a = b
|
|
b = temporaryA
|
|
}
|
|
```
|
|
|
|
<!--
|
|
- test: `genericFunctions`
|
|
|
|
```swifttest
|
|
-> func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
|
|
let temporaryA = a
|
|
a = b
|
|
b = temporaryA
|
|
}
|
|
```
|
|
-->
|
|
|
|
<!--
|
|
This could be done in one line using a tuple pattern: (a, b) = (b, a)
|
|
That's probably not as approachable here, and the novel syntax to avoid an
|
|
explicit placeholder variable might distract from the discussion of
|
|
generics.
|
|
-->
|
|
|
|
The body of the `swapTwoValues(_:_:)` function
|
|
is identical to the body of the `swapTwoInts(_:_:)` function.
|
|
However, the first line of `swapTwoValues(_:_:)`
|
|
is slightly different from `swapTwoInts(_:_:)`.
|
|
Here's how the first lines compare:
|
|
|
|
```swift
|
|
func swapTwoInts(_ a: inout Int, _ b: inout Int)
|
|
func swapTwoValues<T>(_ a: inout T, _ b: inout T)
|
|
```
|
|
|
|
<!--
|
|
- test: `genericFunctionsComparison`
|
|
|
|
```swifttest
|
|
-> func swapTwoInts(_ a: inout Int, _ b: inout Int)
|
|
>> {
|
|
>> let temporaryA = a
|
|
>> a = b
|
|
>> b = temporaryA
|
|
>> }
|
|
-> func swapTwoValues<T>(_ a: inout T, _ b: inout T)
|
|
>> {
|
|
>> let temporaryA = a
|
|
>> a = b
|
|
>> b = temporaryA
|
|
>> }
|
|
```
|
|
-->
|
|
|
|
The generic version of the function
|
|
uses a *placeholder* type name (called `T`, in this case)
|
|
instead of an *actual* type name (such as `Int`, `String`, or `Double`).
|
|
The placeholder type name doesn't say anything about what `T` must be,
|
|
but it *does* say that both `a` and `b` must be of the same type `T`,
|
|
whatever `T` represents.
|
|
The actual type to use in place of `T`
|
|
is determined each time the `swapTwoValues(_:_:)` function is called.
|
|
|
|
The other difference between a generic function and a nongeneric function
|
|
is that the generic function's name (`swapTwoValues(_:_:)`)
|
|
is followed by the placeholder type name (`T`) inside angle brackets (`<T>`).
|
|
The brackets tell Swift that `T` is a placeholder type name
|
|
within the `swapTwoValues(_:_:)` function definition.
|
|
Because `T` is a placeholder, Swift doesn't look for an actual type called `T`.
|
|
|
|
The `swapTwoValues(_:_:)` function can now be called in the same way as `swapTwoInts`,
|
|
except that it can be passed two values of *any* type,
|
|
as long as both of those values are of the same type as each other.
|
|
Each time `swapTwoValues(_:_:)` is called,
|
|
the type to use for `T` is inferred from the types of values passed to the function.
|
|
|
|
In the two examples below, `T` is inferred to be `Int` and `String` respectively:
|
|
|
|
```swift
|
|
var someInt = 3
|
|
var anotherInt = 107
|
|
swapTwoValues(&someInt, &anotherInt)
|
|
// someInt is now 107, and anotherInt is now 3
|
|
|
|
var someString = "hello"
|
|
var anotherString = "world"
|
|
swapTwoValues(&someString, &anotherString)
|
|
// someString is now "world", and anotherString is now "hello"
|
|
```
|
|
|
|
<!--
|
|
- test: `genericFunctions`
|
|
|
|
```swifttest
|
|
-> var someInt = 3
|
|
-> var anotherInt = 107
|
|
-> swapTwoValues(&someInt, &anotherInt)
|
|
/> someInt is now \(someInt), and anotherInt is now \(anotherInt)
|
|
</ someInt is now 107, and anotherInt is now 3
|
|
|
|
-> var someString = "hello"
|
|
-> var anotherString = "world"
|
|
-> swapTwoValues(&someString, &anotherString)
|
|
/> someString is now \"\(someString)\", and anotherString is now \"\(anotherString)\"
|
|
</ someString is now "world", and anotherString is now "hello"
|
|
```
|
|
-->
|
|
|
|
> Note: The `swapTwoValues(_:_:)` function defined above is inspired by
|
|
> a generic function called `swap`, which is part of the Swift standard library,
|
|
> and is automatically made available for you to use in your apps.
|
|
> If you need the behavior of the `swapTwoValues(_:_:)` function in your own code,
|
|
> you can use Swift's existing `swap(_:_:)` function rather than providing your own implementation.
|
|
|
|
## Type Parameters
|
|
|
|
In the `swapTwoValues(_:_:)` example above,
|
|
the placeholder type `T` is an example of a *type parameter*.
|
|
Type parameters specify and name a placeholder type,
|
|
and are written immediately after the function's name,
|
|
between a pair of matching angle brackets (such as `<T>`).
|
|
|
|
Once you specify a type parameter,
|
|
you can use it to define the type of a function's parameters
|
|
(such as the `a` and `b` parameters of the `swapTwoValues(_:_:)` function),
|
|
or as the function's return type,
|
|
or as a type annotation within the body of the function.
|
|
In each case, the type parameter
|
|
is replaced with an *actual* type whenever the function is called.
|
|
(In the `swapTwoValues(_:_:)` example above,
|
|
`T` was replaced with `Int` the first time the function was called,
|
|
and was replaced with `String` the second time it was called.)
|
|
|
|
You can provide more than one type parameter
|
|
by writing multiple type parameter names within the angle brackets,
|
|
separated by commas.
|
|
|
|
## Naming Type Parameters
|
|
|
|
In most cases, type parameters have descriptive names,
|
|
such as `Key` and `Value` in `Dictionary<Key, Value>`
|
|
and `Element` in `Array<Element>`,
|
|
which tells the reader about the relationship between the type parameter
|
|
and the generic type or function it's used in.
|
|
However, when there isn't a meaningful relationship between them,
|
|
it's traditional to name them using single letters such as `T`, `U`, and `V`,
|
|
such as `T` in the `swapTwoValues(_:_:)` function above.
|
|
|
|
Use upper camel case names for type parameters,
|
|
like `T` and `MyTypeParameter`,
|
|
to indicate that they're a placeholder for a *type*, not a value.
|
|
|
|
> Note:
|
|
> If you don't need to name a type parameter
|
|
> and its generic type constraints are simple,
|
|
> there's an alternate, lightweight syntax you can use instead,
|
|
> as described in <doc:OpaqueTypes#Opaque-Parameter-Types>.
|
|
<!--
|
|
Comparison between this syntax and the lightweight syntax
|
|
is in the Opaque Types chapter, not here ---
|
|
the reader hasn't learned about constraints yet,
|
|
so it wouldn't make sense to list what is/isn't supported.
|
|
-->
|
|
|
|
## Generic Types
|
|
|
|
In addition to generic functions,
|
|
Swift enables you to define your own *generic types*.
|
|
These are custom classes, structures, and enumerations
|
|
that can work with *any* type, in a similar way to `Array` and `Dictionary`.
|
|
|
|
This section shows you how to write a generic collection type called `Stack`.
|
|
A stack is an ordered set of values, similar to an array,
|
|
but with a more restricted set of operations than Swift's `Array` type.
|
|
An array allows new items to be inserted and removed at any location in the array.
|
|
A stack, however, allows new items to be appended only to the end of the collection
|
|
(known as *pushing* a new value on to the stack).
|
|
Similarly, a stack allows items to be removed only from the end of the collection
|
|
(known as *popping* a value off the stack).
|
|
|
|
> Note: The concept of a stack is used by the `UINavigationController` class
|
|
> to model the view controllers in its navigation hierarchy.
|
|
> You call the `UINavigationController` class
|
|
> `pushViewController(_:animated:)` method to add (or push)
|
|
> a view controller on to the navigation stack,
|
|
> and its `popViewControllerAnimated(_:)` method to remove (or pop)
|
|
> a view controller from the navigation stack.
|
|
> A stack is a useful collection model whenever you need a strict
|
|
> “last in, first out” approach to managing a collection.
|
|
|
|
The illustration below shows the push and pop behavior for a stack:
|
|
|
|

|
|
|
|
1. There are currently three values on the stack.
|
|
2. A fourth value is pushed onto the top of the stack.
|
|
3. The stack now holds four values, with the most recent one at the top.
|
|
4. The top item in the stack is popped.
|
|
5. After popping a value, the stack once again holds three values.
|
|
|
|
Here's how to write a nongeneric version of a stack,
|
|
in this case for a stack of `Int` values:
|
|
|
|
```swift
|
|
struct IntStack {
|
|
var items: [Int] = []
|
|
mutating func push(_ item: Int) {
|
|
items.append(item)
|
|
}
|
|
mutating func pop() -> Int {
|
|
return items.removeLast()
|
|
}
|
|
}
|
|
```
|
|
|
|
<!--
|
|
- test: `genericStack`
|
|
|
|
```swifttest
|
|
-> struct IntStack {
|
|
var items: [Int] = []
|
|
mutating func push(_ item: Int) {
|
|
items.append(item)
|
|
}
|
|
mutating func pop() -> Int {
|
|
return items.removeLast()
|
|
}
|
|
}
|
|
>> var intStack = IntStack()
|
|
>> intStack.push(1)
|
|
>> intStack.push(2)
|
|
>> intStack.push(3)
|
|
>> intStack.push(4)
|
|
>> print("the stack now contains \(intStack.items.count) integers")
|
|
<< the stack now contains 4 integers
|
|
```
|
|
-->
|
|
|
|
This structure uses an `Array` property called `items` to store the values in the stack.
|
|
`Stack` provides two methods, `push` and `pop`,
|
|
to push and pop values on and off the stack.
|
|
These methods are marked as `mutating`,
|
|
because they need to modify (or *mutate*) the structure's `items` array.
|
|
|
|
The `IntStack` type shown above can only be used with `Int` values, however.
|
|
It would be much more useful to define a *generic* `Stack` structure,
|
|
that can manage a stack of *any* type of value.
|
|
|
|
Here's a generic version of the same code:
|
|
|
|
```swift
|
|
struct Stack<Element> {
|
|
var items: [Element] = []
|
|
mutating func push(_ item: Element) {
|
|
items.append(item)
|
|
}
|
|
mutating func pop() -> Element {
|
|
return items.removeLast()
|
|
}
|
|
}
|
|
```
|
|
|
|
<!--
|
|
- test: `genericStack`
|
|
|
|
```swifttest
|
|
-> struct Stack<Element> {
|
|
var items: [Element] = []
|
|
mutating func push(_ item: Element) {
|
|
items.append(item)
|
|
}
|
|
mutating func pop() -> Element {
|
|
return items.removeLast()
|
|
}
|
|
}
|
|
```
|
|
-->
|
|
|
|
Note how the generic version of `Stack`
|
|
is essentially the same as the nongeneric version,
|
|
but with a type parameter called `Element`
|
|
instead of an actual type of `Int`.
|
|
This type parameter is written within a pair of angle brackets (`<Element>`)
|
|
immediately after the structure's name.
|
|
|
|
`Element` defines a placeholder name for
|
|
a type to be provided later.
|
|
This future type can be referred to as `Element`
|
|
anywhere within the structure's definition.
|
|
In this case, `Element` is used as a placeholder in three places:
|
|
|
|
- To create a property called `items`,
|
|
which is initialized with an empty array of values of type `Element`
|
|
- To specify that the `push(_:)` method has a single parameter called `item`,
|
|
which must be of type `Element`
|
|
- To specify that the value returned by the `pop()` method
|
|
will be a value of type `Element`
|
|
|
|
Because it's a generic type,
|
|
`Stack` can be used to create a stack of *any* valid type in Swift,
|
|
in a similar manner to `Array` and `Dictionary`.
|
|
|
|
You create a new `Stack` instance by writing
|
|
the type to be stored in the stack within angle brackets.
|
|
For example, to create a new stack of strings,
|
|
you write `Stack<String>()`:
|
|
|
|
```swift
|
|
var stackOfStrings = Stack<String>()
|
|
stackOfStrings.push("uno")
|
|
stackOfStrings.push("dos")
|
|
stackOfStrings.push("tres")
|
|
stackOfStrings.push("cuatro")
|
|
// the stack now contains 4 strings
|
|
```
|
|
|
|
<!--
|
|
- test: `genericStack`
|
|
|
|
```swifttest
|
|
-> var stackOfStrings = Stack<String>()
|
|
-> stackOfStrings.push("uno")
|
|
-> stackOfStrings.push("dos")
|
|
-> stackOfStrings.push("tres")
|
|
-> stackOfStrings.push("cuatro")
|
|
/> the stack now contains \(stackOfStrings.items.count) strings
|
|
</ the stack now contains 4 strings
|
|
```
|
|
-->
|
|
|
|
Here's how `stackOfStrings` looks after pushing these four values on to the stack:
|
|
|
|

|
|
|
|
Popping a value from the stack removes and returns the top value, `"cuatro"`:
|
|
|
|
```swift
|
|
let fromTheTop = stackOfStrings.pop()
|
|
// fromTheTop is equal to "cuatro", and the stack now contains 3 strings
|
|
```
|
|
|
|
<!--
|
|
- test: `genericStack`
|
|
|
|
```swifttest
|
|
-> let fromTheTop = stackOfStrings.pop()
|
|
/> fromTheTop is equal to \"\(fromTheTop)\", and the stack now contains \(stackOfStrings.items.count) strings
|
|
</ fromTheTop is equal to "cuatro", and the stack now contains 3 strings
|
|
```
|
|
-->
|
|
|
|
Here's how the stack looks after popping its top value:
|
|
|
|

|
|
|
|
## Extending a Generic Type
|
|
|
|
When you extend a generic type,
|
|
you don't provide a type parameter list as part of the extension's definition.
|
|
Instead, the type parameter list from the *original* type definition
|
|
is available within the body of the extension,
|
|
and the original type parameter names are used to refer to
|
|
the type parameters from the original definition.
|
|
|
|
The following example extends the generic `Stack` type to add
|
|
a read-only computed property called `topItem`,
|
|
which returns the top item on the stack without popping it from the stack:
|
|
|
|
```swift
|
|
extension Stack {
|
|
var topItem: Element? {
|
|
return items.isEmpty ? nil : items[items.count - 1]
|
|
}
|
|
}
|
|
```
|
|
|
|
<!--
|
|
- test: `genericStack`
|
|
|
|
```swifttest
|
|
-> extension Stack {
|
|
var topItem: Element? {
|
|
return items.isEmpty ? nil : items[items.count - 1]
|
|
}
|
|
}
|
|
```
|
|
-->
|
|
|
|
The `topItem` property returns an optional value of type `Element`.
|
|
If the stack is empty, `topItem` returns `nil`;
|
|
if the stack isn't empty, `topItem` returns the final item in the `items` array.
|
|
|
|
Note that this extension doesn't define a type parameter list.
|
|
Instead, the `Stack` type's existing type parameter name, `Element`,
|
|
is used within the extension to indicate the optional type of
|
|
the `topItem` computed property.
|
|
|
|
The `topItem` computed property can now be used with any `Stack` instance
|
|
to access and query its top item without removing it.
|
|
|
|
```swift
|
|
if let topItem = stackOfStrings.topItem {
|
|
print("The top item on the stack is \(topItem).")
|
|
}
|
|
// Prints "The top item on the stack is tres."
|
|
```
|
|
|
|
<!--
|
|
- test: `genericStack`
|
|
|
|
```swifttest
|
|
-> if let topItem = stackOfStrings.topItem {
|
|
print("The top item on the stack is \(topItem).")
|
|
}
|
|
<- The top item on the stack is tres.
|
|
```
|
|
-->
|
|
|
|
Extensions of a generic type can also include requirements
|
|
that instances of the extended type must satisfy
|
|
in order to gain the new functionality,
|
|
as discussed in <doc:Generics#Extensions-with-a-Generic-Where-Clause> below.
|
|
|
|
## Type Constraints
|
|
|
|
The `swapTwoValues(_:_:)` function and the `Stack` type can work with any type.
|
|
However, it's sometimes useful to enforce
|
|
certain *type constraints* on the types that can be used with
|
|
generic functions and generic types.
|
|
Type constraints specify that a type parameter must
|
|
inherit from a specific class,
|
|
or conform to a particular protocol or protocol composition.
|
|
|
|
For example,
|
|
Swift's `Dictionary` type places a limitation on
|
|
the types that can be used as keys for a dictionary.
|
|
As described in <doc:CollectionTypes#Dictionaries>,
|
|
the type of a dictionary's keys must be *hashable*.
|
|
That is, it must provide a way to make itself uniquely representable.
|
|
`Dictionary` needs its keys to be hashable so that it can
|
|
check whether it already contains a value for a particular key.
|
|
Without this requirement, `Dictionary` couldn't tell
|
|
whether it should insert or replace a value for a particular key,
|
|
nor would it be able to find a value for a given key that's already in the dictionary.
|
|
|
|
This requirement is enforced by a type constraint on the key type for `Dictionary`,
|
|
which specifies that the key type must conform to the `Hashable` protocol,
|
|
a special protocol defined in the Swift standard library.
|
|
All of Swift's basic types (such as `String`, `Int`, `Double`, and `Bool`)
|
|
are hashable by default.
|
|
For information about
|
|
making your own custom types conform to the `Hashable` protocol,
|
|
see [Conforming to the Hashable Protocol](https://developer.apple.com/documentation/swift/hashable#2849490).
|
|
|
|
You can define your own type constraints when creating custom generic types,
|
|
and these constraints provide much of the power of generic programming.
|
|
Abstract concepts like `Hashable`
|
|
characterize types in terms of their conceptual characteristics,
|
|
rather than their concrete type.
|
|
|
|
### Type Constraint Syntax
|
|
|
|
You write type constraints by placing a single class or protocol constraint
|
|
after a type parameter's name, separated by a colon,
|
|
as part of the type parameter list.
|
|
The basic syntax for type constraints on a generic function is shown below
|
|
(although the syntax is the same for generic types):
|
|
|
|
```swift
|
|
func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
|
|
// function body goes here
|
|
}
|
|
```
|
|
|
|
<!--
|
|
- test: `typeConstraints`
|
|
|
|
```swifttest
|
|
>> class SomeClass {}
|
|
>> protocol SomeProtocol {}
|
|
-> func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
|
|
// function body goes here
|
|
}
|
|
```
|
|
-->
|
|
|
|
The hypothetical function above has two type parameters.
|
|
The first type parameter, `T`, has a type constraint
|
|
that requires `T` to be a subclass of `SomeClass`.
|
|
The second type parameter, `U`, has a type constraint
|
|
that requires `U` to conform to the protocol `SomeProtocol`.
|
|
|
|
### Type Constraints in Action
|
|
|
|
Here's a nongeneric function called `findIndex(ofString:in:)`,
|
|
which is given a `String` value to find
|
|
and an array of `String` values within which to find it.
|
|
The `findIndex(ofString:in:)` function returns an optional `Int` value,
|
|
which will be the index of the first matching string in the array if it's found,
|
|
or `nil` if the string can't be found:
|
|
|
|
```swift
|
|
func findIndex(ofString valueToFind: String, in array: [String]) -> Int? {
|
|
for (index, value) in array.enumerated() {
|
|
if value == valueToFind {
|
|
return index
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
```
|
|
|
|
<!--
|
|
- test: `typeConstraints`
|
|
|
|
```swifttest
|
|
-> func findIndex(ofString valueToFind: String, in array: [String]) -> Int? {
|
|
for (index, value) in array.enumerated() {
|
|
if value == valueToFind {
|
|
return index
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
```
|
|
-->
|
|
|
|
The `findIndex(ofString:in:)` function can be used to find a string value in an array of strings:
|
|
|
|
```swift
|
|
let strings = ["cat", "dog", "llama", "parakeet", "terrapin"]
|
|
if let foundIndex = findIndex(ofString: "llama", in: strings) {
|
|
print("The index of llama is \(foundIndex)")
|
|
}
|
|
// Prints "The index of llama is 2".
|
|
```
|
|
|
|
<!--
|
|
- test: `typeConstraints`
|
|
|
|
```swifttest
|
|
-> let strings = ["cat", "dog", "llama", "parakeet", "terrapin"]
|
|
-> if let foundIndex = findIndex(ofString: "llama", in: strings) {
|
|
print("The index of llama is \(foundIndex)")
|
|
}
|
|
<- The index of llama is 2
|
|
```
|
|
-->
|
|
|
|
The principle of finding the index of a value in an array isn't useful only for strings, however.
|
|
You can write the same functionality as a generic function
|
|
by replacing any mention of strings with values of some type `T` instead.
|
|
|
|
Here's how you might expect a generic version of `findIndex(ofString:in:)`,
|
|
called `findIndex(of:in:)`, to be written.
|
|
Note that the return type of this function is still `Int?`,
|
|
because the function returns an optional index number,
|
|
not an optional value from the array.
|
|
Be warned, though --- this function doesn't compile,
|
|
for reasons explained after the example:
|
|
|
|
```swift
|
|
func findIndex<T>(of valueToFind: T, in array:[T]) -> Int? {
|
|
for (index, value) in array.enumerated() {
|
|
if value == valueToFind {
|
|
return index
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
```
|
|
|
|
<!--
|
|
- test: `typeConstraints-err`
|
|
|
|
```swifttest
|
|
-> func findIndex<T>(of valueToFind: T, in array:[T]) -> Int? {
|
|
for (index, value) in array.enumerated() {
|
|
if value == valueToFind {
|
|
return index
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
!$ error: binary operator '==' cannot be applied to two 'T' operands
|
|
!! if value == valueToFind {
|
|
!! ~~~~~ ^ ~~~~~~~~~~~
|
|
```
|
|
-->
|
|
|
|
This function doesn't compile as written above.
|
|
The problem lies with the equality check, “`if value == valueToFind`”.
|
|
Not every type in Swift can be compared with the equal to operator (`==`).
|
|
If you create your own class or structure to represent a complex data model, for example,
|
|
then the meaning of “equal to” for that class or structure
|
|
isn't something that Swift can guess for you.
|
|
Because of this, it isn't possible to guarantee that this code will work
|
|
for *every* possible type `T`,
|
|
and an appropriate error is reported when you try to compile the code.
|
|
|
|
All is not lost, however.
|
|
The Swift standard library defines a protocol called `Equatable`,
|
|
which requires any conforming type to implement
|
|
the equal to operator (`==`) and the not equal to operator (`!=`)
|
|
to compare any two values of that type.
|
|
All of Swift's standard types automatically support the `Equatable` protocol.
|
|
|
|
<!--
|
|
TODO: write about how to make your own types conform to Equatable
|
|
once we have some documentation that actually describes it.
|
|
The text to use is something like:
|
|
and you can make your own types conform to ``Equatable`` too,
|
|
as described in <link>.
|
|
-->
|
|
|
|
Any type that's `Equatable` can be used safely with the `findIndex(of:in:)` function,
|
|
because it's guaranteed to support the equal to operator.
|
|
To express this fact, you write a type constraint of `Equatable`
|
|
as part of the type parameter's definition when you define the function:
|
|
|
|
```swift
|
|
func findIndex<T: Equatable>(of valueToFind: T, in array:[T]) -> Int? {
|
|
for (index, value) in array.enumerated() {
|
|
if value == valueToFind {
|
|
return index
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
```
|
|
|
|
<!--
|
|
- test: `typeConstraintsEquatable`
|
|
|
|
```swifttest
|
|
-> func findIndex<T: Equatable>(of valueToFind: T, in array:[T]) -> Int? {
|
|
for (index, value) in array.enumerated() {
|
|
if value == valueToFind {
|
|
return index
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
```
|
|
-->
|
|
|
|
The single type parameter for `findIndex(of:in:)` is written as `T: Equatable`,
|
|
which means “any type `T` that conforms to the `Equatable` protocol.”
|
|
|
|
The `findIndex(of:in:)` function now compiles successfully
|
|
and can be used with any type that's `Equatable`, such as `Double` or `String`:
|
|
|
|
```swift
|
|
let doubleIndex = findIndex(of: 9.3, in: [3.14159, 0.1, 0.25])
|
|
// doubleIndex is an optional Int with no value, because 9.3 isn't in the array
|
|
let stringIndex = findIndex(of: "Andrea", in: ["Mike", "Malcolm", "Andrea"])
|
|
// stringIndex is an optional Int containing a value of 2
|
|
```
|
|
|
|
<!--
|
|
- test: `typeConstraintsEquatable`
|
|
|
|
```swifttest
|
|
-> let doubleIndex = findIndex(of: 9.3, in: [3.14159, 0.1, 0.25])
|
|
/> doubleIndex is an optional Int with no value, because 9.3 isn't in the array
|
|
</ doubleIndex is an optional Int with no value, because 9.3 isn't in the array
|
|
-> let stringIndex = findIndex(of: "Andrea", in: ["Mike", "Malcolm", "Andrea"])
|
|
/> stringIndex is an optional Int containing a value of \(stringIndex!)
|
|
</ stringIndex is an optional Int containing a value of 2
|
|
```
|
|
-->
|
|
|
|
<!--
|
|
TODO: providing different type parameters on individual methods within a generic type
|
|
-->
|
|
|
|
<!--
|
|
TODO: likewise providing type parameters for initializers
|
|
-->
|
|
|
|
## Associated Types
|
|
|
|
When defining a protocol,
|
|
it's sometimes useful to declare one or more associated types
|
|
as part of the protocol's definition.
|
|
An *associated type* gives a placeholder name
|
|
to a type that's used as part of the protocol.
|
|
The actual type to use for that associated type
|
|
isn't specified until the protocol is adopted.
|
|
Associated types are specified with the `associatedtype` keyword.
|
|
|
|
### Associated Types in Action
|
|
|
|
Here's an example of a protocol called `Container`,
|
|
which declares an associated type called `Item`:
|
|
|
|
```swift
|
|
protocol Container {
|
|
associatedtype Item
|
|
mutating func append(_ item: Item)
|
|
var count: Int { get }
|
|
subscript(i: Int) -> Item { get }
|
|
}
|
|
```
|
|
|
|
<!--
|
|
- test: `associatedTypes, associatedTypes-err`
|
|
|
|
```swifttest
|
|
-> protocol Container {
|
|
associatedtype Item
|
|
mutating func append(_ item: Item)
|
|
var count: Int { get }
|
|
subscript(i: Int) -> Item { get }
|
|
}
|
|
```
|
|
-->
|
|
|
|
The `Container` protocol defines three required capabilities
|
|
that any container must provide:
|
|
|
|
- It must be possible to add a new item to the container with an `append(_:)` method.
|
|
- It must be possible to access a count of the items in the container
|
|
through a `count` property that returns an `Int` value.
|
|
- It must be possible to retrieve each item in the container with a subscript
|
|
that takes an `Int` index value.
|
|
|
|
This protocol doesn't specify how the items in the container should be stored
|
|
or what type they're allowed to be.
|
|
The protocol only specifies the three bits of functionality
|
|
that any type must provide in order to be considered a `Container`.
|
|
A conforming type can provide additional functionality,
|
|
as long as it satisfies these three requirements.
|
|
|
|
Any type that conforms to the `Container` protocol must be able to specify
|
|
the type of values it stores.
|
|
Specifically, it must ensure that only items of the right type
|
|
are added to the container,
|
|
and it must be clear about the type of the items returned by its subscript.
|
|
|
|
To define these requirements,
|
|
the `Container` protocol needs a way to refer to
|
|
the type of the elements that a container will hold,
|
|
without knowing what that type is for a specific container.
|
|
The `Container` protocol needs to specify that
|
|
any value passed to the `append(_:)` method
|
|
must have the same type as the container's element type,
|
|
and that the value returned by the container's subscript
|
|
will be of the same type as the container's element type.
|
|
|
|
To achieve this,
|
|
the `Container` protocol declares an associated type called `Item`,
|
|
written as `associatedtype Item`.
|
|
The protocol doesn't define what `Item` is ---
|
|
that information is left for any conforming type to provide.
|
|
Nonetheless, the `Item` alias provides a way to refer to
|
|
the type of the items in a `Container`,
|
|
and to define a type for use with the `append(_:)` method and subscript,
|
|
to ensure that the expected behavior of any `Container` is enforced.
|
|
|
|
Here's a version of the nongeneric `IntStack` type
|
|
from <doc:Generics#Generic-Types> above,
|
|
adapted to conform to the `Container` protocol:
|
|
|
|
```swift
|
|
struct IntStack: Container {
|
|
// original IntStack implementation
|
|
var items: [Int] = []
|
|
mutating func push(_ item: Int) {
|
|
items.append(item)
|
|
}
|
|
mutating func pop() -> Int {
|
|
return items.removeLast()
|
|
}
|
|
// conformance to the Container protocol
|
|
typealias Item = Int
|
|
mutating func append(_ item: Int) {
|
|
self.push(item)
|
|
}
|
|
var count: Int {
|
|
return items.count
|
|
}
|
|
subscript(i: Int) -> Int {
|
|
return items[i]
|
|
}
|
|
}
|
|
```
|
|
|
|
<!--
|
|
- test: `associatedTypes`
|
|
|
|
```swifttest
|
|
-> struct IntStack: Container {
|
|
// original IntStack implementation
|
|
var items: [Int] = []
|
|
mutating func push(_ item: Int) {
|
|
items.append(item)
|
|
}
|
|
mutating func pop() -> Int {
|
|
return items.removeLast()
|
|
}
|
|
// conformance to the Container protocol
|
|
typealias Item = Int
|
|
mutating func append(_ item: Int) {
|
|
self.push(item)
|
|
}
|
|
var count: Int {
|
|
return items.count
|
|
}
|
|
subscript(i: Int) -> Int {
|
|
return items[i]
|
|
}
|
|
}
|
|
```
|
|
-->
|
|
|
|
The `IntStack` type implements all three of the `Container` protocol's requirements,
|
|
and in each case wraps part of the `IntStack` type's existing functionality
|
|
to satisfy these requirements.
|
|
|
|
Moreover, `IntStack` specifies that for this implementation of `Container`,
|
|
the appropriate `Item` to use is a type of `Int`.
|
|
The definition of `typealias Item = Int` turns the abstract type of `Item`
|
|
into a concrete type of `Int` for this implementation of the `Container` protocol.
|
|
|
|
Thanks to Swift's type inference,
|
|
you don't actually need to declare a concrete `Item` of `Int`
|
|
as part of the definition of `IntStack`.
|
|
Because `IntStack` conforms to all of the requirements of the `Container` protocol,
|
|
Swift can infer the appropriate `Item` to use,
|
|
simply by looking at the type of the `append(_:)` method's `item` parameter
|
|
and the return type of the subscript.
|
|
Indeed, if you delete the `typealias Item = Int` line from the code above,
|
|
everything still works, because it's clear what type should be used for `Item`.
|
|
|
|
You can also make the generic `Stack` type conform to the `Container` protocol:
|
|
|
|
```swift
|
|
struct Stack<Element>: Container {
|
|
// original Stack<Element> implementation
|
|
var items: [Element] = []
|
|
mutating func push(_ item: Element) {
|
|
items.append(item)
|
|
}
|
|
mutating func pop() -> Element {
|
|
return items.removeLast()
|
|
}
|
|
// conformance to the Container protocol
|
|
mutating func append(_ item: Element) {
|
|
self.push(item)
|
|
}
|
|
var count: Int {
|
|
return items.count
|
|
}
|
|
subscript(i: Int) -> Element {
|
|
return items[i]
|
|
}
|
|
}
|
|
```
|
|
|
|
<!--
|
|
- test: `associatedTypes, associatedTypes-err`
|
|
|
|
```swifttest
|
|
-> struct Stack<Element>: Container {
|
|
// original Stack<Element> implementation
|
|
var items: [Element] = []
|
|
mutating func push(_ item: Element) {
|
|
items.append(item)
|
|
}
|
|
mutating func pop() -> Element {
|
|
return items.removeLast()
|
|
}
|
|
// conformance to the Container protocol
|
|
mutating func append(_ item: Element) {
|
|
self.push(item)
|
|
}
|
|
var count: Int {
|
|
return items.count
|
|
}
|
|
subscript(i: Int) -> Element {
|
|
return items[i]
|
|
}
|
|
}
|
|
```
|
|
-->
|
|
|
|
This time, the type parameter `Element` is used as
|
|
the type of the `append(_:)` method's `item` parameter
|
|
and the return type of the subscript.
|
|
Swift can therefore infer that `Element` is the appropriate type to use
|
|
as the `Item` for this particular container.
|
|
|
|
### Extending an Existing Type to Specify an Associated Type
|
|
|
|
You can extend an existing type to add conformance to a protocol,
|
|
as described in <doc:Protocols#Adding-Protocol-Conformance-with-an-Extension>.
|
|
This includes a protocol with an associated type.
|
|
|
|
Swift's `Array` type already provides an `append(_:)` method,
|
|
a `count` property, and a subscript with an `Int` index to retrieve its elements.
|
|
These three capabilities match the requirements of the `Container` protocol.
|
|
This means that you can extend `Array` to conform to the `Container` protocol
|
|
simply by declaring that `Array` adopts the protocol.
|
|
You do this with an empty extension,
|
|
as described in <doc:Protocols#Declaring-Protocol-Adoption-with-an-Extension>:
|
|
|
|
```swift
|
|
extension Array: Container {}
|
|
```
|
|
|
|
<!--
|
|
- test: `associatedTypes`
|
|
|
|
```swifttest
|
|
-> extension Array: Container {}
|
|
```
|
|
-->
|
|
|
|
Array's existing `append(_:)` method and subscript enable Swift to infer
|
|
the appropriate type to use for `Item`,
|
|
just as for the generic `Stack` type above.
|
|
After defining this extension, you can use any `Array` as a `Container`.
|
|
|
|
### Adding Constraints to an Associated Type
|
|
|
|
You can add type constraints to an associated type in a protocol
|
|
to require that conforming types satisfy those constraints.
|
|
For example,
|
|
the following code defines a version of `Container`
|
|
that requires the items in the container to be equatable.
|
|
|
|
```swift
|
|
protocol Container {
|
|
associatedtype Item: Equatable
|
|
mutating func append(_ item: Item)
|
|
var count: Int { get }
|
|
subscript(i: Int) -> Item { get }
|
|
}
|
|
```
|
|
|
|
<!--
|
|
- test: `associatedTypes-equatable`
|
|
|
|
```swifttest
|
|
-> protocol Container {
|
|
associatedtype Item: Equatable
|
|
mutating func append(_ item: Item)
|
|
var count: Int { get }
|
|
subscript(i: Int) -> Item { get }
|
|
}
|
|
```
|
|
-->
|
|
|
|
To conform to this version of `Container`,
|
|
the container's `Item` type has to conform to the `Equatable` protocol.
|
|
|
|
### Using a Protocol in Its Associated Type's Constraints
|
|
|
|
A protocol can appear as part of its own requirements.
|
|
For example,
|
|
here's a protocol that refines the `Container` protocol,
|
|
adding the requirement of a `suffix(_:)` method.
|
|
The `suffix(_:)` method
|
|
returns a given number of elements from the end of the container,
|
|
storing them in an instance of the `Suffix` type.
|
|
|
|
```swift
|
|
protocol SuffixableContainer: Container {
|
|
associatedtype Suffix: SuffixableContainer where Suffix.Item == Item
|
|
func suffix(_ size: Int) -> Suffix
|
|
}
|
|
```
|
|
|
|
<!--
|
|
- test: `associatedTypes`
|
|
|
|
```swifttest
|
|
-> protocol SuffixableContainer: Container {
|
|
associatedtype Suffix: SuffixableContainer where Suffix.Item == Item
|
|
func suffix(_ size: Int) -> Suffix
|
|
}
|
|
```
|
|
-->
|
|
|
|
In this protocol,
|
|
`Suffix` is an associated type,
|
|
like the `Item` type in the `Container` example above.
|
|
`Suffix` has two constraints:
|
|
It must conform to the `SuffixableContainer` protocol
|
|
(the protocol currently being defined),
|
|
and its `Item` type must be the same
|
|
as the container's `Item` type.
|
|
The constraint on `Item` is a generic `where` clause,
|
|
which is discussed in <doc:Generics#Associated-Types-with-a-Generic-Where-Clause> below.
|
|
|
|
Here's an extension of the `Stack` type
|
|
from <doc:Generics#Generic-Types> above
|
|
that adds conformance to the `SuffixableContainer` protocol:
|
|
|
|
```swift
|
|
extension Stack: SuffixableContainer {
|
|
func suffix(_ size: Int) -> Stack {
|
|
var result = Stack()
|
|
for index in (count-size)..<count {
|
|
result.append(self[index])
|
|
}
|
|
return result
|
|
}
|
|
// Inferred that Suffix is Stack.
|
|
}
|
|
var stackOfInts = Stack<Int>()
|
|
stackOfInts.append(10)
|
|
stackOfInts.append(20)
|
|
stackOfInts.append(30)
|
|
let suffix = stackOfInts.suffix(2)
|
|
// suffix contains 20 and 30
|
|
```
|
|
|
|
<!--
|
|
- test: `associatedTypes`
|
|
|
|
```swifttest
|
|
-> extension Stack: SuffixableContainer {
|
|
func suffix(_ size: Int) -> Stack {
|
|
var result = Stack()
|
|
for index in (count-size)..<count {
|
|
result.append(self[index])
|
|
}
|
|
return result
|
|
}
|
|
// Inferred that Suffix is Stack.
|
|
}
|
|
-> var stackOfInts = Stack<Int>()
|
|
-> stackOfInts.append(10)
|
|
-> stackOfInts.append(20)
|
|
-> stackOfInts.append(30)
|
|
>> assert(stackOfInts.suffix(0).items == [])
|
|
-> let suffix = stackOfInts.suffix(2)
|
|
// suffix contains 20 and 30
|
|
>> assert(suffix.items == [20, 30])
|
|
```
|
|
-->
|
|
|
|
In the example above,
|
|
the `Suffix` associated type for `Stack` is also `Stack`,
|
|
so the suffix operation on `Stack` returns another `Stack`.
|
|
Alternatively,
|
|
a type that conforms to `SuffixableContainer`
|
|
can have a `Suffix` type that's different from itself ---
|
|
meaning the suffix operation can return a different type.
|
|
For example,
|
|
here's an extension to the nongeneric `IntStack` type
|
|
that adds `SuffixableContainer` conformance,
|
|
using `Stack<Int>` as its suffix type instead of `IntStack`:
|
|
|
|
```swift
|
|
extension IntStack: SuffixableContainer {
|
|
func suffix(_ size: Int) -> Stack<Int> {
|
|
var result = Stack<Int>()
|
|
for index in (count-size)..<count {
|
|
result.append(self[index])
|
|
}
|
|
return result
|
|
}
|
|
// Inferred that Suffix is Stack<Int>.
|
|
}
|
|
```
|
|
|
|
<!--
|
|
- test: `associatedTypes`
|
|
|
|
```swifttest
|
|
-> extension IntStack: SuffixableContainer {
|
|
func suffix(_ size: Int) -> Stack<Int> {
|
|
var result = Stack<Int>()
|
|
for index in (count-size)..<count {
|
|
result.append(self[index])
|
|
}
|
|
return result
|
|
}
|
|
// Inferred that Suffix is Stack<Int>.
|
|
}
|
|
>> var intStack = IntStack()
|
|
>> intStack.append(10)
|
|
>> intStack.append(20)
|
|
>> intStack.append(30)
|
|
>> assert(intStack.suffix(0).items == [])
|
|
>> assert(intStack.suffix(2).items == [20, 30])
|
|
```
|
|
-->
|
|
|
|
## Generic Where Clauses
|
|
|
|
Type constraints, as described in <doc:Generics#Type-Constraints>,
|
|
enable you to define requirements on the type parameters associated with
|
|
a generic function, subscript, or type.
|
|
|
|
It can also be useful to define requirements for associated types.
|
|
You do this by defining a *generic where clause*.
|
|
A generic `where` clause enables you to require that
|
|
an associated type must conform to a certain protocol,
|
|
or that certain type parameters and associated types must be the same.
|
|
A generic `where` clause starts with the `where` keyword,
|
|
followed by constraints for associated types
|
|
or equality relationships between types and associated types.
|
|
You write a generic `where` clause right before the opening curly brace
|
|
of a type or function's body.
|
|
|
|
The example below defines a generic function called `allItemsMatch`,
|
|
which checks to see if two `Container` instances contain
|
|
the same items in the same order.
|
|
The function returns a Boolean value of `true` if all items match
|
|
and a value of `false` if they don't.
|
|
|
|
The two containers to be checked don't have to be
|
|
the same type of container (although they can be),
|
|
but they do have to hold the same type of items.
|
|
This requirement is expressed through a combination of type constraints
|
|
and a generic `where` clause:
|
|
|
|
```swift
|
|
func allItemsMatch<C1: Container, C2: Container>
|
|
(_ someContainer: C1, _ anotherContainer: C2) -> Bool
|
|
where C1.Item == C2.Item, C1.Item: Equatable {
|
|
|
|
// Check that both containers contain the same number of items.
|
|
if someContainer.count != anotherContainer.count {
|
|
return false
|
|
}
|
|
|
|
// Check each pair of items to see if they're equivalent.
|
|
for i in 0..<someContainer.count {
|
|
if someContainer[i] != anotherContainer[i] {
|
|
return false
|
|
}
|
|
}
|
|
|
|
// All items match, so return true.
|
|
return true
|
|
}
|
|
```
|
|
|
|
<!--
|
|
- test: `associatedTypes`
|
|
|
|
```swifttest
|
|
-> func allItemsMatch<C1: Container, C2: Container>
|
|
(_ someContainer: C1, _ anotherContainer: C2) -> Bool
|
|
where C1.Item == C2.Item, C1.Item: Equatable {
|
|
|
|
// Check that both containers contain the same number of items.
|
|
if someContainer.count != anotherContainer.count {
|
|
return false
|
|
}
|
|
|
|
// Check each pair of items to see if they're equivalent.
|
|
for i in 0..<someContainer.count {
|
|
if someContainer[i] != anotherContainer[i] {
|
|
return false
|
|
}
|
|
}
|
|
|
|
// All items match, so return true.
|
|
return true
|
|
}
|
|
```
|
|
-->
|
|
|
|
This function takes two arguments called
|
|
`someContainer` and `anotherContainer`.
|
|
The `someContainer` argument is of type `C1`,
|
|
and the `anotherContainer` argument is of type `C2`.
|
|
Both `C1` and `C2` are type parameters
|
|
for two container types to be determined when the function is called.
|
|
|
|
The following requirements are placed on the function's two type parameters:
|
|
|
|
- `C1` must conform to the `Container` protocol (written as `C1: Container`).
|
|
- `C2` must also conform to the `Container` protocol (written as `C2: Container`).
|
|
- The `Item` for `C1` must be the same as the `Item` for `C2`
|
|
(written as `C1.Item == C2.Item`).
|
|
- The `Item` for `C1` must conform to the `Equatable` protocol
|
|
(written as `C1.Item: Equatable`).
|
|
|
|
The first and second requirements are defined in the function's type parameter list,
|
|
and the third and fourth requirements are defined in the function's generic `where` clause.
|
|
|
|
These requirements mean:
|
|
|
|
- `someContainer` is a container of type `C1`.
|
|
- `anotherContainer` is a container of type `C2`.
|
|
- `someContainer` and `anotherContainer` contain the same type of items.
|
|
- The items in `someContainer` can be checked with the not equal operator (`!=`)
|
|
to see if they're different from each other.
|
|
|
|
The third and fourth requirements combine to mean that
|
|
the items in `anotherContainer` can *also* be checked with the `!=` operator,
|
|
because they're exactly the same type as the items in `someContainer`.
|
|
|
|
These requirements enable the `allItemsMatch(_:_:)` function to compare the two containers,
|
|
even if they're of a different container type.
|
|
|
|
The `allItemsMatch(_:_:)` function starts by checking that
|
|
both containers contain the same number of items.
|
|
If they contain a different number of items, there's no way that they can match,
|
|
and the function returns `false`.
|
|
|
|
After making this check, the function iterates over all of the items in `someContainer`
|
|
with a `for`-`in` loop and the half-open range operator (`..<`).
|
|
For each item, the function checks whether the item from `someContainer` isn't equal to
|
|
the corresponding item in `anotherContainer`.
|
|
If the two items aren't equal, then the two containers don't match,
|
|
and the function returns `false`.
|
|
|
|
If the loop finishes without finding a mismatch,
|
|
the two containers match, and the function returns `true`.
|
|
|
|
Here's how the `allItemsMatch(_:_:)` function looks in action:
|
|
|
|
```swift
|
|
var stackOfStrings = Stack<String>()
|
|
stackOfStrings.push("uno")
|
|
stackOfStrings.push("dos")
|
|
stackOfStrings.push("tres")
|
|
|
|
var arrayOfStrings = ["uno", "dos", "tres"]
|
|
|
|
if allItemsMatch(stackOfStrings, arrayOfStrings) {
|
|
print("All items match.")
|
|
} else {
|
|
print("Not all items match.")
|
|
}
|
|
// Prints "All items match."
|
|
```
|
|
|
|
<!--
|
|
- test: `associatedTypes`
|
|
|
|
```swifttest
|
|
-> var stackOfStrings = Stack<String>()
|
|
-> stackOfStrings.push("uno")
|
|
-> stackOfStrings.push("dos")
|
|
-> stackOfStrings.push("tres")
|
|
|
|
-> var arrayOfStrings = ["uno", "dos", "tres"]
|
|
|
|
-> if allItemsMatch(stackOfStrings, arrayOfStrings) {
|
|
print("All items match.")
|
|
} else {
|
|
print("Not all items match.")
|
|
}
|
|
<- All items match.
|
|
```
|
|
-->
|
|
|
|
The example above creates a `Stack` instance to store `String` values,
|
|
and pushes three strings onto the stack.
|
|
The example also creates an `Array` instance initialized with
|
|
an array literal containing the same three strings as the stack.
|
|
Even though the stack and the array are of a different type,
|
|
they both conform to the `Container` protocol,
|
|
and both contain the same type of values.
|
|
You can therefore call the `allItemsMatch(_:_:)` function
|
|
with these two containers as its arguments.
|
|
In the example above, the `allItemsMatch(_:_:)` function correctly reports that
|
|
all of the items in the two containers match.
|
|
|
|
## Extensions with a Generic Where Clause
|
|
|
|
You can also use a generic `where` clause as part of an extension.
|
|
The example below
|
|
extends the generic `Stack` structure from the previous examples
|
|
to add an `isTop(_:)` method.
|
|
|
|
```swift
|
|
extension Stack where Element: Equatable {
|
|
func isTop(_ item: Element) -> Bool {
|
|
guard let topItem = items.last else {
|
|
return false
|
|
}
|
|
return topItem == item
|
|
}
|
|
}
|
|
```
|
|
|
|
<!--
|
|
- test: `associatedTypes`
|
|
|
|
```swifttest
|
|
-> extension Stack where Element: Equatable {
|
|
func isTop(_ item: Element) -> Bool {
|
|
guard let topItem = items.last else {
|
|
return false
|
|
}
|
|
return topItem == item
|
|
}
|
|
}
|
|
```
|
|
-->
|
|
|
|
This new `isTop(_:)` method
|
|
first checks that the stack isn't empty,
|
|
and then compares the given item
|
|
against the stack's topmost item.
|
|
If you tried to do this without a generic `where` clause,
|
|
you would have a problem:
|
|
The implementation of `isTop(_:)` uses the `==` operator,
|
|
but the definition of `Stack` doesn't require
|
|
its items to be equatable,
|
|
so using the `==` operator results in a compile-time error.
|
|
Using a generic `where` clause
|
|
lets you add a new requirement to the extension,
|
|
so that the extension adds the `isTop(_:)` method
|
|
only when the items in the stack are equatable.
|
|
|
|
Here's how the `isTop(_:)` method looks in action:
|
|
|
|
```swift
|
|
if stackOfStrings.isTop("tres") {
|
|
print("Top element is tres.")
|
|
} else {
|
|
print("Top element is something else.")
|
|
}
|
|
// Prints "Top element is tres."
|
|
```
|
|
|
|
<!--
|
|
- test: `associatedTypes`
|
|
|
|
```swifttest
|
|
-> if stackOfStrings.isTop("tres") {
|
|
print("Top element is tres.")
|
|
} else {
|
|
print("Top element is something else.")
|
|
}
|
|
<- Top element is tres.
|
|
```
|
|
-->
|
|
|
|
If you try to call the `isTop(_:)` method
|
|
on a stack whose elements aren't equatable,
|
|
you'll get a compile-time error.
|
|
|
|
```swift
|
|
struct NotEquatable { }
|
|
var notEquatableStack = Stack<NotEquatable>()
|
|
let notEquatableValue = NotEquatable()
|
|
notEquatableStack.push(notEquatableValue)
|
|
notEquatableStack.isTop(notEquatableValue) // Error
|
|
```
|
|
|
|
<!--
|
|
- test: `associatedTypes-err`
|
|
|
|
```swifttest
|
|
-> struct NotEquatable { }
|
|
-> var notEquatableStack = Stack<NotEquatable>()
|
|
-> let notEquatableValue = NotEquatable()
|
|
-> notEquatableStack.push(notEquatableValue)
|
|
-> notEquatableStack.isTop(notEquatableValue) // Error
|
|
!$ error: value of type 'Stack<NotEquatable>' has no member 'isTop'
|
|
!! notEquatableStack.isTop(notEquatableValue) // Error
|
|
!! ~~~~~~~~~~~~~~~~~ ^~~~~
|
|
```
|
|
-->
|
|
|
|
You can use a generic `where` clause with extensions to a protocol.
|
|
The example below extends the `Container` protocol from the previous examples
|
|
to add a `startsWith(_:)` method.
|
|
|
|
```swift
|
|
extension Container where Item: Equatable {
|
|
func startsWith(_ item: Item) -> Bool {
|
|
return count >= 1 && self[0] == item
|
|
}
|
|
}
|
|
```
|
|
|
|
<!--
|
|
- test: `associatedTypes`
|
|
|
|
```swifttest
|
|
-> extension Container where Item: Equatable {
|
|
func startsWith(_ item: Item) -> Bool {
|
|
return count >= 1 && self[0] == item
|
|
}
|
|
}
|
|
```
|
|
-->
|
|
|
|
<!--
|
|
Using Container rather than Sequence/Collection
|
|
to continue running with the same example through the chapter.
|
|
This does, however, mean I can't use a for-in loop.
|
|
-->
|
|
|
|
The `startsWith(_:)` method
|
|
first makes sure that the container has at least one item,
|
|
and then it checks
|
|
whether the first item in the container matches the given item.
|
|
This new `startsWith(_:)` method
|
|
can be used with any type that conforms to the `Container` protocol,
|
|
including the stacks and arrays used above,
|
|
as long as the container's items are equatable.
|
|
|
|
```swift
|
|
if [9, 9, 9].startsWith(42) {
|
|
print("Starts with 42.")
|
|
} else {
|
|
print("Starts with something else.")
|
|
}
|
|
// Prints "Starts with something else."
|
|
```
|
|
|
|
<!--
|
|
- test: `associatedTypes`
|
|
|
|
```swifttest
|
|
-> if [9, 9, 9].startsWith(42) {
|
|
print("Starts with 42.")
|
|
} else {
|
|
print("Starts with something else.")
|
|
}
|
|
<- Starts with something else.
|
|
```
|
|
-->
|
|
|
|
The generic `where` clause in the example above
|
|
requires `Item` to conform to a protocol,
|
|
but you can also write a generic `where` clauses that require `Item`
|
|
to be a specific type.
|
|
For example:
|
|
|
|
```swift
|
|
extension Container where Item == Double {
|
|
func average() -> Double {
|
|
var sum = 0.0
|
|
for index in 0..<count {
|
|
sum += self[index]
|
|
}
|
|
return sum / Double(count)
|
|
}
|
|
}
|
|
print([1260.0, 1200.0, 98.6, 37.0].average())
|
|
// Prints "648.9".
|
|
```
|
|
|
|
<!--
|
|
- test: `associatedTypes`
|
|
|
|
```swifttest
|
|
-> extension Container where Item == Double {
|
|
func average() -> Double {
|
|
var sum = 0.0
|
|
for index in 0..<count {
|
|
sum += self[index]
|
|
}
|
|
return sum / Double(count)
|
|
}
|
|
}
|
|
-> print([1260.0, 1200.0, 98.6, 37.0].average())
|
|
<- 648.9
|
|
```
|
|
-->
|
|
|
|
This example adds an `average()` method
|
|
to containers whose `Item` type is `Double`.
|
|
It iterates over the items in the container to add them up,
|
|
and divides by the container's count to compute the average.
|
|
It explicitly converts the count from `Int` to `Double`
|
|
to be able to do floating-point division.
|
|
|
|
You can include multiple requirements in a generic `where` clause
|
|
that's part of an extension,
|
|
just like you can for a generic `where` clause that you write elsewhere.
|
|
Separate each requirement in the list with a comma.
|
|
|
|
<!--
|
|
No example of a compound where clause
|
|
because Container only has one generic part ---
|
|
there isn't anything to write a second constraint for.
|
|
-->
|
|
|
|
## Contextual Where Clauses
|
|
|
|
You can write a generic `where` clause
|
|
as part of a declaration that doesn't have its own generic type constraints,
|
|
when you're already working in the context of generic types.
|
|
For example,
|
|
you can write a generic `where` clause
|
|
on a subscript of a generic type
|
|
or on a method in an extension to a generic type.
|
|
The `Container` structure is generic,
|
|
and the `where` clauses in the example below
|
|
specify what type constraints have to be satisfied
|
|
to make these new methods available on a container.
|
|
|
|
```swift
|
|
extension Container {
|
|
func average() -> Double where Item == Int {
|
|
var sum = 0.0
|
|
for index in 0..<count {
|
|
sum += Double(self[index])
|
|
}
|
|
return sum / Double(count)
|
|
}
|
|
func endsWith(_ item: Item) -> Bool where Item: Equatable {
|
|
return count >= 1 && self[count-1] == item
|
|
}
|
|
}
|
|
let numbers = [1260, 1200, 98, 37]
|
|
print(numbers.average())
|
|
// Prints "648.75".
|
|
print(numbers.endsWith(37))
|
|
// Prints "true".
|
|
```
|
|
|
|
<!--
|
|
- test: `associatedTypes`
|
|
|
|
```swifttest
|
|
-> extension Container {
|
|
func average() -> Double where Item == Int {
|
|
var sum = 0.0
|
|
for index in 0..<count {
|
|
sum += Double(self[index])
|
|
}
|
|
return sum / Double(count)
|
|
}
|
|
func endsWith(_ item: Item) -> Bool where Item: Equatable {
|
|
return count >= 1 && self[count-1] == item
|
|
}
|
|
}
|
|
-> let numbers = [1260, 1200, 98, 37]
|
|
-> print(numbers.average())
|
|
<- 648.75
|
|
-> print(numbers.endsWith(37))
|
|
<- true
|
|
```
|
|
-->
|
|
|
|
This example
|
|
adds an `average()` method to `Container` when the items are integers,
|
|
and it adds an `endsWith(_:)` method when the items are equatable.
|
|
Both functions include a generic `where` clause
|
|
that adds type constraints to the generic `Item` type parameter
|
|
from the original declaration of `Container`.
|
|
|
|
If you want to write this code without using contextual `where` clauses,
|
|
you write two extensions,
|
|
one for each generic `where` clause.
|
|
The example above and the example below have the same behavior.
|
|
|
|
```swift
|
|
extension Container where Item == Int {
|
|
func average() -> Double {
|
|
var sum = 0.0
|
|
for index in 0..<count {
|
|
sum += Double(self[index])
|
|
}
|
|
return sum / Double(count)
|
|
}
|
|
}
|
|
extension Container where Item: Equatable {
|
|
func endsWith(_ item: Item) -> Bool {
|
|
return count >= 1 && self[count-1] == item
|
|
}
|
|
}
|
|
```
|
|
|
|
<!--
|
|
- test: `associatedTypes-err`
|
|
|
|
```swifttest
|
|
-> extension Container where Item == Int {
|
|
func average() -> Double {
|
|
var sum = 0.0
|
|
for index in 0..<count {
|
|
sum += Double(self[index])
|
|
}
|
|
return sum / Double(count)
|
|
}
|
|
}
|
|
extension Container where Item: Equatable {
|
|
func endsWith(_ item: Item) -> Bool {
|
|
return count >= 1 && self[count-1] == item
|
|
}
|
|
}
|
|
```
|
|
-->
|
|
|
|
In the version of this example that uses contextual `where` clauses,
|
|
the implementation of `average()` and `endsWith(_:)`
|
|
are both in the same extension
|
|
because each method's generic `where` clause
|
|
states the requirements that need to be satisfied
|
|
to make that method available.
|
|
Moving those requirements to the extensions' generic `where` clauses
|
|
makes the methods available in the same situations,
|
|
but requires one extension per requirement.
|
|
|
|
## Associated Types with a Generic Where Clause
|
|
|
|
You can include a generic `where` clause on an associated type.
|
|
For example, suppose you want to make a version of `Container`
|
|
that includes an iterator,
|
|
like what the `Sequence` protocol uses in the Swift standard library.
|
|
Here's how you write that:
|
|
|
|
```swift
|
|
protocol Container {
|
|
associatedtype Item
|
|
mutating func append(_ item: Item)
|
|
var count: Int { get }
|
|
subscript(i: Int) -> Item { get }
|
|
|
|
associatedtype Iterator: IteratorProtocol where Iterator.Element == Item
|
|
func makeIterator() -> Iterator
|
|
}
|
|
```
|
|
|
|
<!--
|
|
- test: `associatedTypes-iterator`
|
|
|
|
```swifttest
|
|
-> protocol Container {
|
|
associatedtype Item
|
|
mutating func append(_ item: Item)
|
|
var count: Int { get }
|
|
subscript(i: Int) -> Item { get }
|
|
|
|
associatedtype Iterator: IteratorProtocol where Iterator.Element == Item
|
|
func makeIterator() -> Iterator
|
|
}
|
|
```
|
|
-->
|
|
|
|
<!--
|
|
Adding makeIterator() to Container lets it conform to Sequence,
|
|
although we don't call that out here.
|
|
-->
|
|
|
|
The generic `where` clause on `Iterator` requires that
|
|
the iterator must traverse over elements
|
|
of the same item type as the container's items,
|
|
regardless of the iterator's type.
|
|
The `makeIterator()` function provides access to a container's iterator.
|
|
|
|
<!--
|
|
This example requires SE-0157 Recursive protocol constraints
|
|
which is tracked by rdar://20531108
|
|
|
|
that accepts a ranged of indexes it its subscript
|
|
and returns a subcontainer ---
|
|
similar to how ``Collection`` works in the Swift standard library.
|
|
|
|
.. testcode:: associatedTypes-subcontainer
|
|
|
|
-> protocol Container {
|
|
associatedtype Item
|
|
associatedtype SubContainer: Container where SubContainer.Item == Item
|
|
|
|
mutating func append(_ item: Item)
|
|
var count: Int { get }
|
|
subscript(i: Int) -> Item { get }
|
|
subscript(range: Range<Int>) -> SubContainer { get }
|
|
}
|
|
|
|
The generic ``where`` clause on ``SubContainer`` requires that
|
|
the subcontainer must have the same item type as the container has,
|
|
regardless of what type the subcontainer is.
|
|
The original container and the subcontainer
|
|
could be represented by the same type
|
|
or by different types.
|
|
The new subscript that accepts a range
|
|
uses this new associated type as its return value.
|
|
-->
|
|
|
|
For a protocol that inherits from another protocol,
|
|
you add a constraint to an inherited associated type
|
|
by including the generic `where` clause in the protocol declaration.
|
|
For example, the following code
|
|
declares a `ComparableContainer` protocol
|
|
that requires `Item` to conform to `Comparable`:
|
|
|
|
```swift
|
|
protocol ComparableContainer: Container where Item: Comparable { }
|
|
```
|
|
|
|
<!--
|
|
- test: `associatedTypes`
|
|
|
|
```swifttest
|
|
-> protocol ComparableContainer: Container where Item: Comparable { }
|
|
```
|
|
-->
|
|
|
|
<!--
|
|
This version throws a warning as of Swift commit de66b0c25c70:
|
|
"redeclaration of associated type %0 from protocol %1 is better
|
|
expressed as a 'where' clause on the protocol"
|
|
|
|
-> protocol ComparableContainer: Container {
|
|
associatedtype Item: Comparable
|
|
}
|
|
-->
|
|
|
|
<!--
|
|
Exercise the new container -- this might not actually be needed,
|
|
and it adds a level of complexity.
|
|
|
|
function < (lhs: ComparableContainer, rhs: ComparableContainer) -> Bool {
|
|
// Sort empty containers before nonempty containers.
|
|
if lhs.count == 0 {
|
|
return true
|
|
} else if rhs.count == 0 {
|
|
return false
|
|
}
|
|
|
|
// Sort nonempty containers by their first element.
|
|
// (In real code, you would want to compare the second element
|
|
// if the first elements are equal, and so on.)
|
|
return lhs[0] < rhs[0]
|
|
}
|
|
-->
|
|
|
|
## Generic Subscripts
|
|
|
|
Subscripts can be generic,
|
|
and they can include generic `where` clauses.
|
|
You write the placeholder type name inside angle brackets after `subscript`,
|
|
and you write a generic `where` clause right before the opening curly brace
|
|
of the subscript's body.
|
|
For example:
|
|
|
|
<!--
|
|
The paragraph above borrows the wording used to introduce
|
|
generics and 'where' clauses earlier in this chapter.
|
|
-->
|
|
|
|
```swift
|
|
extension Container {
|
|
subscript<Indices: Sequence>(indices: Indices) -> [Item]
|
|
where Indices.Iterator.Element == Int {
|
|
var result: [Item] = []
|
|
for index in indices {
|
|
result.append(self[index])
|
|
}
|
|
return result
|
|
}
|
|
}
|
|
```
|
|
|
|
<!--
|
|
- test: `genericSubscript`
|
|
|
|
```swifttest
|
|
>> protocol Container {
|
|
>> associatedtype Item
|
|
>> mutating func append(_ item: Item)
|
|
>> var count: Int { get }
|
|
>> subscript(i: Int) -> Item { get }
|
|
>> }
|
|
-> extension Container {
|
|
subscript<Indices: Sequence>(indices: Indices) -> [Item]
|
|
where Indices.Iterator.Element == Int {
|
|
var result: [Item] = []
|
|
for index in indices {
|
|
result.append(self[index])
|
|
}
|
|
return result
|
|
}
|
|
}
|
|
```
|
|
-->
|
|
|
|
<!--
|
|
- test: `genericSubscript`
|
|
|
|
```swifttest
|
|
>> struct IntStack: Container {
|
|
// original IntStack implementation
|
|
var items: [Int] = []
|
|
mutating func push(_ item: Int) {
|
|
items.append(item)
|
|
}
|
|
mutating func pop() -> Int {
|
|
return items.removeLast()
|
|
}
|
|
// conformance to the Container protocol
|
|
typealias Item = Int
|
|
mutating func append(_ item: Int) {
|
|
self.push(item)
|
|
}
|
|
var count: Int {
|
|
return items.count
|
|
}
|
|
subscript(i: Int) -> Int {
|
|
return items[i]
|
|
}
|
|
}
|
|
>> var s = IntStack()
|
|
>> s.push(10); s.push(20); s.push(30)
|
|
>> let items = s[ [0, 2] ]
|
|
>> assert(items == [10, 30])
|
|
```
|
|
-->
|
|
|
|
This extension to the `Container` protocol
|
|
adds a subscript that takes a sequence of indices
|
|
and returns an array containing the items at each given index.
|
|
This generic subscript is constrained as follows:
|
|
|
|
- The generic parameter `Indices` in angle brackets
|
|
has to be a type that conforms to the `Sequence` protocol
|
|
from the Swift standard library.
|
|
- The subscript takes a single parameter, `indices`,
|
|
which is an instance of that `Indices` type.
|
|
- The generic `where` clause requires
|
|
that the iterator for the sequence
|
|
must traverse over elements of type `Int`.
|
|
This ensures that the indices in the sequence
|
|
are the same type as the indices used for a container.
|
|
|
|
Taken together, these constraints mean that
|
|
the value passed for the `indices` parameter
|
|
is a sequence of integers.
|
|
|
|
## Implicit Constraints
|
|
|
|
In addition to constraints you write explicitly,
|
|
many places in your generic code also implicitly require
|
|
conformance to some very common protocols like [`Copyable`][].
|
|
<!-- When SE-0446 is implemented, add Escapable above. -->
|
|
These generic constraints that you don't have to write
|
|
are known as *implicit constraints*.
|
|
For example, both of the following function declarations
|
|
require `MyType` to be copyable:
|
|
|
|
[`Copyable`]: https://developer.apple.com/documentation/swift/copyable
|
|
|
|
```swift
|
|
function someFunction<MyType> { ... }
|
|
function someFunction<MyType: Copyable> { ... }
|
|
```
|
|
|
|
In the code above,
|
|
the first declaration has an implicit constraint,
|
|
and the second version lists the conformance explicitly.
|
|
In most code,
|
|
types also implicitly conform to these common protocols.
|
|
For more information,
|
|
see <doc:Protocols#Implicit-Conformance-to-a-Protocol>.
|
|
|
|
Because most types in Swift conform to these protocols,
|
|
writing them almost everywhere would be repetitive.
|
|
Instead, by marking only the exceptions,
|
|
you call out the places that omit a common constraint.
|
|
To suppress an implicit constraint,
|
|
write the protocol name with a tilde (`~`) in front of it.
|
|
You can read `~Copyable` as "maybe copyable" ---
|
|
this suppressed constraint allows
|
|
both copyable and noncopyable types in this position.
|
|
Note that `~Copyable` doesn't *require* the type to be noncopyable.
|
|
For example:
|
|
|
|
```swift
|
|
func f<MyType>(x: inout MyType) {
|
|
let x1 = x // The value of x1 is a copy of x's value.
|
|
let x2 = x // The value of x2 is a copy of x's value.
|
|
}
|
|
|
|
func g<AnotherType: ~Copyable>(y: inout AnotherType) {
|
|
let y1 = y // The assignment consumes y's value.
|
|
let y2 = y // Error: Value consumed more than once.
|
|
}
|
|
```
|
|
|
|
In the code above,
|
|
the function `f()` implicitly requires `MyType` to be copyable.
|
|
Within the function body,
|
|
the value of `x` is copied to `x1` and `x2` in the assignment.
|
|
In contrast, `g()` suppresses the implicit constraint on `AnotherType`,
|
|
which allows you to pass either a copyable or noncopyable value.
|
|
Within the function body,
|
|
you can't copy the value of `y`
|
|
because `AnotherType` might be noncopyable.
|
|
Assignment consumes the value of `y`
|
|
and it's an error to consume that value more than once.
|
|
Noncopyable values like `y`
|
|
must be passed as in-out, borrowing, or consuming parameters ---
|
|
for more information,
|
|
see <doc:Declarations#Borrowing-and-Consuming-Parameters>.
|
|
|
|
For details about when generic code
|
|
includes an implicit constraint to a given protocol,
|
|
see the reference for that protocol.
|
|
|
|
<!--
|
|
TODO: Generic Enumerations
|
|
--------------------------
|
|
-->
|
|
|
|
<!--
|
|
TODO: Describe how Optional<Wrapped> works
|
|
-->
|
|
|
|
<!--
|
|
This source file is part of the Swift.org open source project
|
|
|
|
Copyright (c) 2014 - 2022 Apple Inc. and the Swift project authors
|
|
Licensed under Apache License v2.0 with Runtime Library Exception
|
|
|
|
See https://swift.org/LICENSE.txt for license information
|
|
See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
|
-->
|