Generics are placeholders for types, allowing you to write flexible code that can be applied across multiple types. The advantage of using generics over Any
is that they still allow the compiler to enforce strong type-safety.
A generic placeholder is defined within angle brackets <>
.
For functions, this placeholder is placed after the function name:
/// Picks one of the inputs at random, and returns it
func pickRandom<T>(_ a:T, _ b:T) -> T {
return arc4random_uniform(2) == 0 ? a : b
}
In this case, the generic placeholder is T
. When you come to call the function, Swift can infer the type of T
for you (as it simply acts as a placeholder for an actual type).
let randomOutput = pickRandom(5, 7) // returns an Int (that's either 5 or 7)
Here we’re passing two integers to the function. Therefore Swift is inferring T == Int
– thus the function signature is inferred to be (Int, Int) -> Int
.
Because of the strong type safety that generics offer – both the arguments and return of the function must be the same type. Therefore the following will not compile:
struct Foo {}
let foo = Foo()
let randomOutput = pickRandom(foo, 5) // error: cannot convert value of type 'Int' to expected argument type 'Foo'
In order to use generics with classes, structs or enums, you can define the generic placeholder after the type name.
class Bar<T> {
var baz : T
init(baz:T) {
self.baz = baz
}
}
This generic placeholder will require a type when you come to use the class Bar
. In this case, it can be inferred from the initialiser init(baz:T)
.
let bar = Bar(baz: "a string") // bar's type is Bar<String>
Here the generic placeholder T
is inferred to be of type String
, thus creating a Bar<String>
instance. You can also specify the type explicitly:
let bar = Bar<String>(baz: "a string")
When used with a type, the given generic placeholder will keep its type for the entire lifetime of the given instance, and cannot be changed after initialisation. Therefore when you access the property baz
, it will always be of type String
for this given instance.
let str = bar.baz // of type String
When you come to pass around generic types, in most cases you have to be explicit about the generic placeholder type you expect. For example, as a function input:
func takeABarInt(bar:Bar<Int>) {
...
}
This function will only accept a Bar<Int>
. Attempting to pass in a Bar
instance where the generic placeholder type is not Int
will result in a compiler error.
Generic placeholder names are not just limited to single letters. If a given placeholder represents a meaningful concept, you should give it a descriptive name. For example, Swift’s Array
has a generic placeholder called Element
, which defines the element type of a given Array
instance.
public struct Array<Element> : RandomAccessCollection, MutableCollection {
...
}