Generics Part 1 – Functions and Types

There are eleventy billion articles about Generics in Swift on the web and this is article number eleventy billion and one – part 1. This is the first in a series of posts where we will take an in-depth look at the syntax of Swift generics.

Why Generics?

Generics allow us to write powerful and flexible code that is reusable without loss of type safety. For motivation, let’s take a look at an example blatantly lifted straight off the official Swift guide.

func swapValues(_ a: inout Int, _ b: inout Int) {
  let temp = a
  a = b
  b = temp
}

It is a simple function for swapping the values contained in two Ints. If we want to swap Doubles, we would write:

func swapValues(_ a: inout Double, _ b: inout Double) {
  let temp = a
  a = b
  b = temp
}

The two functions above are overloaded versions of each other. It is obvious that the two function bodies have literally the same code. To avoid such code duplication, Swift has support for writing generic functions that can be reused across types.

What does the swapValues function do? It swaps two values of the same Type. That is the only requirement. It doesn’t matter what that particular type is. How would you write a single function that works for any type? I will give you an example of a ridiculously wrong answer, first:

func swapValues(_ a: inout Any, _ b: inout Any) {
  // same code as before
}

It wouldn’t even compile because a and b are not of the same type. An Int value cannot be swapped with a Double value. We have to impose the requirement that they need to be the same type, what ever that type maybe.

We need a way to “name” the Type when defining the function, just like we name regular variables (a and b above), into which values are passed. We can then use this name as the type of both a and b, thus satisfying the requirement that they be of the same type. Put another way, we need a placeholder that will be filled later when the function is called. You already know that regular parameters are filled in with values when the function is called. Likewise, Type parameters should also be filled in when the function is called.

Type parameters

Without further ado, let’s look at how a generic function with type parameters looks like:

func swapValues<T>(_ a: inout T, _ b: inout T) {
  // same code as before
}

Angular brackets right next to the function name is special syntax for Generic Parameter List. This is where we declare the Type Parameters. Once a type parameter is declared, it is available for use elsewhere in the function signature or body. So, they can be used for function arguments, return values and inside the function body. Furthermore, there is no restriction on the number of type parameters.

In the above example, T is the placeholder name for the type, and the same type is used for both input parameters.

Calling a generic function

When we call a generic function, the type is inferred from the type of the arguments passed in (or it could be inferred some other way, such as type casting). You should not and can not specify the actual type within angular brackets, when calling the function. It is not supported syntax. At run time, multiple overloaded versions of the same functions are created and used depending on the type that is inferred.

var x = 10
var y = 5
swapValues(&x, &y) // The Int version of swapValues is used.

Generic Types

Functions or methods are not the only things which can be genericalized (not a real word). Types i.e. classes, structs and enums can be written once in a generic fashion and an instance with a concrete type will be created during initialization. The syntax to define a generic type is the similar to the one used when defining a generic function.

We will use the quintessential example of a Stack to illustrate generic types. First, a non-generic version of a Stack of integers.

struct IntStack {
  var items = [Int]()
  mutating func push(_ item: Int) {
    items.append(item)
  }
  mutating func pop() -> Int {
    return items.removeLast()
  }
}

var stackOfInts = IntStack(items: [1, 2])

Then, a non-generic version of a stack of Strings.

struct StringStack {
  var items = [String]()
  mutating func push(_ item: String) {
    items.append(item)
  }
  mutating func pop() -> String {
    return items.removeLast()
  }
}

var stackOfStrings = StringStack(items: ["1", "2"])

Once again, it is achingly apparent that code should be reused. The only disparity between the two implementations is the type of elements that can be stored into and returned from the stack.

Why not just support one version of the stack that can store any type of elements?

struct AnyStack {
  var items = [Any]()
  mutating func push(_ item: Any) {
    items.append(item)
  }
  mutating func pop() -> Any {
    return items.removeLast()
  }
}

var stackOfAnys = AnyStack(items: ["1", 2])

What’s the problem with such a definition? Well, type safety would be lost and the calling code will become brittle. The elements that are popped from the stack should be typecast before use, like so:

let item = stackOfAnys.pop() as? Int

Having written a lot of poor, repetitive code, it is finally time to examine the generic definition of such a stack.

struct Stack<Element> { // type parameter declared 
  var items = [Element]() // type parameter can be used inside the definition
  mutating func push(_ item: Element) {
    items.append(item)
  }
  mutating func pop() -> Element {
    return items.removeLast()
  }
}

var stackOfInts = Stack(items: [1, 2]) // Element inferred to Int
var stackOfStrings = Stack(items: ["1", "2"]) // Element inferred to String
var stackOfAnys = Stack(items: ["1", 2]) // this compiles, if you were wondering. Element inferred to Any

The type parameter is named Element. As soon as it is declared inside the angular brackets placed after the name of the type, it becomes available for use anywhere inside the definition.

Element is filled in automatically when a stack is created, as shown above. In contrast to generic functions, it is also possible to declare the exact type of Element when the stack is created, like var stack = Stack<Int>(items: []). In fact, this type of explicit declaration is necessary when we declare a variable where inference is not possible:

private var stack: Stack<Int>

It is important to understand that Stack<Int> and Stack<String> are separate types and one cannot be used in place of the other, guaranteeing type safety. A Stack<Int> always pops an Int and a Stack<String> always pops out a String.

Finally, it goes without saying that the generic parameter list for types can contain multiple Type parameters.

Generic Classes

Classes are slightly special in that they support inheritance. Both generic and specialized versions of generic classes can be inherited from.

// A generic class definition
class BaseClass<T> {
  // definition
}

// Subclassing a generic class
class SubClass<S>: BaseClass<S> {
  // S is just another name for T
}

// Subclassing a specialized version of a generic class
class IntSubClass: BaseClass<Int> { // Type explicitly set to Int
  // definition
}

Quiz Time!

We will wrap up this part with a short quiz.

  1. Is this valid syntax?
func swapValues<Int>(_ a: inout Int, _ b: inout Int) { 
  // definition
}

2. Is this valid syntax?

struct Stack<Int> {
  private var items: [Int]

  init(items: [Int]) {
    self.items = items
  }

  var itemCount: Swift.Int { items.count }
}

let stackOfStrings = Stack(items: ["1"])
print(stackOfStrings.itemCount)

3. Is this valid syntax?

class BaseClass<T> {
  // definition
}

class SubClass: BaseClass<T> {
  // definition
}

4. What is printed?

func myPrint<T>(_ a: T) {
  print("generic")
}

func myPrint(_ a: Int) {
  print("special")
}

myPrint(5)

Answers:

  1. Yes. Int is just a type parameter name, having nothing to do with Swift’s inbuilt Integer type
  2. Yes. Int is just a type parameter name. To disambiguate, use Swift.Int
  3. No.
  4. special is always printed. When a generic and a specialized version are available, the specialized version is always chosen.

Conclusion

Generics allow a great amount of code reuse and is behind a signification portion of the Swift standard library. I hope this blog post made the basic syntax clear. They can be made even more powerful by constraining the type parameters to classes or protocols, and this will be the subject of Part 2. 

1 Comment

Leave a Reply