On Stack Overflow, user Carpsen90 asked a question about what protocol to use in a generic function such that it could take any number – Int, Float, Double, etc – without having to revert to NSNumber. This has bitten me a few times, since the integer types (Int, UInt, and their kin) conform to the IntegerType protocol, while Float and Double conform to FloatingPointType, so there’s no shared protocol between them. It’s a little crazy that this isn’t possible right out of the box in Swift, but it does make for a good example of how implementing your own protocols can open up new ways of using generics.

For this post, we’ll implement a minusOneSquared function that gets a number n and returns (n - 1)^2. Our first attempt, without any generic constraints, doesn’t compile:

func minusOneSquared<T>(number: T) -> T {
    let minusOne = number - 1
    > error: cannot invoke '-' with an argument list of type '(T, IntegerLiteralConvertible)'
    return minusOne * minusOne
    > error: cannot invoke '*' with an argument list of type '(T, T)'
}

To write a generic function like this, we’ll first need to create a new protocol declared with whatever methods and operators we’re going to use in the function. The exact details of what the protocol declares will depend on what the generic function needs to do. Our new protocol, NumericType, has the arithmetic operators (so we can multiply and subtract) and an initializer that takes an Int (so we can create a one to subtract from n).

protocol NumericType {
    func +(lhs: Self, rhs: Self) -> Self
    func -(lhs: Self, rhs: Self) -> Self
    func *(lhs: Self, rhs: Self) -> Self
    func /(lhs: Self, rhs: Self) -> Self
    func %(lhs: Self, rhs: Self) -> Self
    init(_ v: Int)
}

All of the numeric types already implement these, but at this point the compiler doesn’t know that they conform to the new NumericType protocol. We have to make this explicit by using an empty extension – Apple calls this “declaring protocol adoption with an extension.” We’ll do this for Double, Float, and all ten integer types:

extension Double : NumericType {}
extension Float  : NumericType {}
extension Int    : NumericType {}
extension Int8   : NumericType {}
extension Int16  : NumericType {}
extension Int32  : NumericType {}
extension Int64  : NumericType {}
extension UInt   : NumericType {}
extension UInt8  : NumericType {}
extension UInt16 : NumericType {}
extension UInt32 : NumericType {}
extension UInt64 : NumericType {}

Finally, we can write a working function, using the NumericType protocol as a generic constraint, and call it with any numeric type we like.

func minusOneSquared<T : NumericType>(number: T) -> T {
    // uses init(_ v: Int) and the - operator
    let minusOne = number - T(1)
    
    // uses * operator
    return minusOne * minusOne
}

minusOneSquared(5)              // 16
minusOneSquared(2.3)            // 1.69
minusOneSquared(2 as UInt64)    // 1