Understanding Swift protocol Associated types

Generics for protocols are what they are.

They enable us to write a common protocol that can accomodate variance in data types, for the same names, in the classes that conform to the protocol

(NOTE: THIS IS STATEMENT IS JUST MY ATTEMPT TO DEFINE associatedtype IN A SINGLE SENTENCE. DO NOT GET TURNED AWAY BY IT! :) ).

Say for example we want to create different structs to represent various types of numbers.

We want to represent a floating point number and an integer number type, and provide functionality in these types to make them print themselves:

struct IntegerNumber: Number {
var value: Int
func printValue() {
print(“Integer: \(value)”)
}
}
struct FloatingPointNumber: Number {
var value: Float
func printValue() {
print(“Float: \(value)”)
}
}

Now since the functionality is common, we can abstract the printValue to a protocol and make our structs conform to it:

protocol Number {
func printValue()
}
struct IntegerNumber: Number {
var value: Int
func printValue() {
print(“Integer: \(value)”)
}
}
struct FloatingPointNumber: Number {
var value: Float
func printValue() {
print(“Float: \(value)”)
}
}

Now we can use a variable of type Number to hold instances of our structs, and call the Number methods on them:

let number: Number = FloatingPointNumber(value: 0.0) // or IntegerNumber(value: 0)number.printValue()

Using the number variable, we can only call the printValue() defined in the protocol.

Now suppose we want to access the underlying ‘value’ directly, so that we can print it ourselves, what shall we do?

We can move the commonly named ‘value’ to the protocol, but the types are different (IntegerNumber has value of type Int,

FloatingPointNumber has value of type Float)!

Enter Swift’s associatedtypes! 🙏

protocol Number {
associatedtype NumberType
var value: NumberType { get } func printValue()
}

Now, for this protocol, the compiler looks for the following in the conforming classes or structs:

1) variable value (var value: Int, var value: Float, etc)

2) method printValue (the usual)

3) Explicit definition of ‘NumberType’.

OR

it tries to get the NumberType by looking at the type of instance variable ‘value’ .

We can make the types conform to the protocol in multiple ways:

// FORM 1struct IntegerNumber: Number {
var value: Int // compiler decides NumberType is Int
func printValue() {
print(“Ingeger: \(value)”)
}
}
// FORM 2
struct FloatingPointNumber: Number {
typealias NumberType = Float // explicit definitition
var value: NumberType func printValue() {
print(“Float: \(value)”)
}
}

In FORM 1, we let let the compiler deduce the type of NumberType by looking at type of ‘value’ property.

In FORM 2, we explicitly TELL the compiler what ‘NumberType’ is.

Nice! Now we can …

// ERROR: Protocol ‘Number’ can only be used as a generic constraint because it has Self or associated type requirements
let number: Number = FloatingPointNumber(value: 0.0)

Error!

FIX: use ‘some’ keyword.

let number: some Number = FloatingPointNumber(value: 0.0) // or IntegerNumber(value: 0)print(number.value)

Why: https://02infinity.medium.com/really-understanding-swift-some-keyword-and-swift-opaque-types-consequently-ae87b3e2efe4

Thanks for reading!