Generics Introduction in Swift

Generics is an excellent way of writing reusable functions for multiple types. If you are already a developer, you have already been using generics in your code. Dictionary and Array is a great example of generics, we can define an array of any type, and that’s what makes it a generic. The flexibility of choosing a type for our Array and Dictionaries is the key functionality of using Generics.

Let’s look at how a dictionary type is defined, and what happens if we try to push a value of some other type.

// Creating a Dictionary with String type as the KEY, and Int type as the VALUE
var dict: [String: Int] = ["One" : 1]

//The line below gives a Compile error: cannot assign value of type '[String]' to subscript of type 'Int'
dict["Two"] = ["II"]

The compiler gives an error as we tried to set a value of different type other than an Int.

The type declared while initialization of the dictionary cannot be changed, though you have so many options, the most common choice is using Any while declaring a type for value in Dictionary. While working with dictionaries, we deal with multiple types of data in many cases.

Any here helps us push data of all the types we have available on Swift, sounds so much like a generic, but it is not, as Generics are type-safe, and Any is not.

Generic is very powerful, and we have not even started on it yet.

Let’s look at a non-generic function that squares our Int values and then finally add them.

func squareAndAddInt(_ a: Int,_ b: Int) -> Int {
    return (a * a) + (b * b)
}

let intResult = squareAndAdd(5, 10)
// Output = 125

If we want to do this with a value of type Double, we’ll need to declare another function that takes two Double values and returns a Double.

func squareAndAddDouble(_ a: Double,_ b: Double) -> Double {
    return (a * a) + (b * b)
}

If you look at the code, the code for square and add is exactly the same except for their types, and that’s where Generic comes in to increase reusability in our code. Let me show you how we can write a generic function and achieve the same functionality.

func squareAndAdd<T>(_ a: T, _ b: T) -> T {
    return (a * a) + (b * b)
}

T here acts as a placeholder type for the values we are going to pass. So, if we pass Int values, the T here will act as a Int, but there’s a problem here, the code doesn’t compile, and there’s a very good reason for this.

The operation done here is a mathematical operation, which cannot be done on values other than the numeric type. So what do we do?

// Conforming Generic type "T" to Numberic protocol
func squareAndAdd<T: Numeric>(_ a: T, _ b: T) -> T {
    return (a * a) + (b * b)
}

let intResult = squareAndAdd(5, 10)
let doubleResult = squareAndAdd(5.5, 10.5)

/* Output:
 intResult = 125
 doubleResult = 140.5
 */
 
// Compile error: function 'squareAndAdd' requires that 'String' conform to 'Numeric'
let stringResult = squareAndAdd("one", "two")

If you look at the example above, and you can also try to execute this in the Playground file. We are successfully able to get result for both Int and Double type. If we try to pass a String value, the compiler gives an error stating that “squareAndAdd requires that String conform to Numeric”.

This is how you can apply restrictions to types that can be passed in your generic function, and because of the function being generic, we don’t need to write the same code for all the other Numeric types. There’s more to learn about Generics, I’ll post more articles on generic soon, so don’t forget to subscribe.

I hope you guys enjoyed reading my article. There’s a lot more to learn together, so subscribe to stay updated about my upcoming articles.

If you have any suggestions or questions, Feel free to connect me on Twitter or Reddit. 😉

Concurrency Programming with Operation Queues - Part 1

How to use Result in Swift?