Skip to content
Theory · Protocols and generics · lesson 1 of 9

Protocols

What you'll learn

What you'll learn:

  • describe with a protocol what a type must be able to do, and declare that your own structs conform to it;
  • keep values of different types in one [any Shape] array and call each value's own method;
  • add abilities to existing types and give a protocol a default implementation;
  • adopt the standard protocols and write generic functions with a type constraint.
A board with the job description Shape and the task area(), with a circle, a square and a triangle wearing Shape badges

Picture a notice board with a job opening called "Shape". The job description lists one task: being able to work out your own area. Whoever can do that gets the job and wears a badge with the job's name.

A circle, a square and a triangle apply. Each one works out its area in its own way: the square multiplies its side by itself, and the triangle takes half of the base times the height. The employer does not care how. What matters is that anyone wearing the badge can answer the question "what is your area?".

Why would a program need this? Say you need to add up the areas of all the shapes on a drawing. A square and a circle are different structs, and an array holds values of one type. Structs never have a shared parent, the way dogs and cats share Animal in an [Animal] array. You need a shared type, "any shape", and in Swift a protocol provides it.

A protocol is a job description

A protocol lists what a type must be able to do, but does not say how to do it. Each item on that list is called a requirement. Let's describe the "Shape" job and hire a square for it:

SWIFT
protocol Shape {
    var name: String { get }
    func area() -> Double
}

struct Square: Shape {
    let name = "square"
    let side: Double
    func area() -> Double { side * side }
}

let tile = Square(side: 3)
print(tile.name, tile.area()) // square 9.0

Let's go through the program part by part:

  1. protocol Shape declares a protocol. Like a type's name, its name starts with a capital letter.
  2. var name: String { get } is a property requirement: a shape must have a property name that can be read.
  3. func area() -> Double is a method requirement. It has no body: the type itself decides how to compute the area.
  4. struct Square: Shape is the badge on the square. The colon and the protocol name after the type's name say that the square takes this job.
  5. Square has both name and area(), so the compiler agrees: the requirements are met.

A type that meets all of a protocol's requirements and declares it with a colon conforms to the protocol. The compiler does not let a badge through without the abilities behind it.

A property from a requirement

The requirement { get } says only one thing: the property can be read. How to store it is up to the type:

  • The square's name is a stored constant, let name = "square". It already has its value and will never get another one, so a square is created without name: Square(side: 3).
  • A circle can compute it every time: var name: String { "circle" }.

In the protocol itself a property is always declared with var, even when it will only be read: { get } is what says so. Writing let in a protocol does not compile.

A protocol stores nothing itself. The property name lives in the square, and the protocol only requires that it exists.

A protocol as a type

You can write a protocol's name wherever you write a type. any Shape means "a value of any type that conforms to Shape". Let's add a circle and put the square and the circle in one array:

SWIFT
struct Circle: Shape {
    let radius: Double
    var name: String { "circle" }
    func area() -> Double { 3.14 * radius * radius }
}

let shapes: [any Shape] = [tile, Circle(radius: 2)]
for shape in shapes {
    print(shape.name, shape.area(), terminator: "; ") // square 9.0; circle 12.56;
}

The loop does not know whether it has a square or a circle. It calls shape.area(), and Swift finds the method of the type whose value is in shape right now. Without the protocol you could not build this array: Square and Circle are different types, and they have no shared type.

Press "Next step" and watch the column of frames: the same line, shape.area(), leads into the square's method once and into the triangle's method the next time.

LiveEach shape works out its own areastep 1 of 8 · frames: 1

The [any Shape] array holds values of two different types: a square and a triangle.

Watch the top frame: its name changes although the call stays the same

The word any stresses that the array can hold values of different types. In older code you will see the form without it, [Shape], and it means the same thing.

Here the job-opening picture is inaccurate. A person can have the needed skills without a badge. In Swift, a type gets the job only if it declares the conformance itself: a struct with an area() method but without : Shape cannot go into an [any Shape] array.

A protocol or a base class

Interviewers like to ask this: how does a protocol differ from a base class? A base class is a parent class that subclasses inherit from. There are three main differences:

  • only classes inherit from a class, while structs, enums and classes can all conform to a protocol;
  • a class has one parent class, while a type can adopt several protocols, separated by commas: struct Square: Shape, Named, if there is also a protocol Named;
  • a base class passes its stored properties on to its subclasses, while a protocol stores nothing: the type itself stores the properties from the requirements.

Common mistake

The badge is on, but a requirement is met differently from what the protocol says: the triangle's area is returned as an Int.

SWIFT
struct Triangle: Shape { // ✗
    let name = "triangle"
    let base: Double
    let height: Double
    func area() -> Int { Int(base * height / 2) }
}
TEXT
error: type 'Triangle' does not conform to protocol 'Shape'

The program does not compile. The protocol requires area() -> Double, while the triangle's method has a different result type, so it does not meet the requirement. The compiler checks requirements before the program runs. The fix is to return a Double: func area() -> Double { base * height / 2 }.

Key points

  • A protocol lists requirements — properties and methods — and the type decides how to meet them.
  • A type conforms to a protocol when it declares so with a colon and meets every requirement; the compiler checks this before the program runs.
  • An [any Shape] array holds values of different types, and the call shape.area() lands in the method of the type whose value is in shape.
  • Unlike a base class, a protocol stores nothing, fits structs and enums, and a type can adopt several protocols.