Start learning free

LESSON 162 OF 415 · SWIFT ESSENTIALS

Protocols in Swift: A Contract for Capabilities

Lesson Read the full explanation

So far, every type you've used — Int, String, your own structs — stood alone. A protocol lets you describe a capability that many unrelated types can share. It's a contract: it lists WHAT a conforming type must provide, and writes no bodies at all:

protocol Describable {
    func describe() -> String
}

Any type can sign the contract by putting : Describable after its name and implementing every requirement:

struct Dog: Describable {
    func describe() -> String { return "Dog named Rex" }
}

Once a type conforms, the protocol itself can be used as a type. An array of [any Describable] happily mixes a Dog and a City — two types with no ancestor in common — because each one guarantees it knows how to describe() itself. When you call item.describe() in a loop, Swift picks the right implementation for whichever concrete type is really there. One interface, many behaviors.


The trap:

The contract is not a suggestion. Write : Describable and then "forget" the method:

struct Dog: Describable {
}

...and the compiler refuses to build your program:

error: type 'Dog' does not conform to protocol 'Describable'
note: protocol requires function 'describe()' with type '() -> String'; add a stub for conformance

Read it bottom-up: the error says which type broke its promise, and the note points at the exact requirement that's missing. Implement describe() with the exact same name, parameters and return type, and the error disappears. This strictness is the feature: if it compiles, every element of [any Describable] is guaranteed to answer describe().


Assignment
Follow the Coding Tutorial and put one protocol in charge of two unrelated types:

  • declare a protocol Describable requiring func describe() -> String,
  • make struct Dog conform, returning "Dog named Rex",
  • make struct City conform, returning "City of Paris",
  • put one of each into let items: [any Describable],
  • loop over items and print each element's describe().

The exact expected output is:

Dog named Rex
City of Paris

Hint
Inside the protocol, the method has no body — just its signature. Inside each struct, it must have a body. The loop variable's type is any Describable, and that's all the loop needs to know.


STEP 1 OF 9
    Ctrl + Enter to check

    SWIFT · SWIFT ESSENTIALS

    Protocols Basics in Swift, explained

    What a protocol is

    A protocol describes a capability that many unrelated types can share. It is a contract: it lists what a conforming type must provide, and it writes no method bodies.

    protocol Describable {
        func describe() -> String
    }
    

    Any type can sign the contract by putting : Describable after its name and implementing every requirement:

    struct Dog: Describable {
        func describe() -> String { return "Dog named Rex" }
    }
    

    Structs, classes and enums can all conform. Two types that have nothing else in common, such as a Dog and a City, can both conform to the same protocol, and code that only needs the capability can treat them alike.

    Using a protocol as a type

    Once types conform, the protocol can be used as a type in its own right. An array of [any Describable] can hold a Dog and a City side by side. The word any marks the element type as "some value whose type conforms to Describable", and the concrete type may differ from element to element.

    When a loop calls item.describe(), Swift runs the implementation belonging to the actual type of that element. One interface gives many behaviors. This is called polymorphism.

    Step by step

    1. Declare the protocol. protocol Describable { } is written like a struct, with the keyword protocol.
    2. Add the requirement. Inside it, write the signature only: func describe() -> String. There are no braces and no body.
    3. Create Dog. struct Dog: Describable { } promises to follow the contract. The compiler now checks that promise.
    4. Implement the method in Dog. It must have the same name, parameters and return type, and this time it has a body: func describe() -> String { return "Dog named Rex" }.
    5. Create City the same way, returning "City of Paris". It is a separate type with no shared ancestor, so the protocol is the only thing connecting it to Dog.
    6. Make the array. let items: [any Describable] = [Dog(), City()]. The type annotation is what allows two different types in one array.
    7. Loop and print. for item in items { print(item.describe()) }. The loop only needs to know that every item can describe itself.

    The solution in Swift

    protocol Describable {
        func describe() -> String
    }
    struct Dog: Describable {
        func describe() -> String { return "Dog named Rex" }
    }
    struct City: Describable {
        func describe() -> String { return "City of Paris" }
    }
    let items: [any Describable] = [Dog(), City()]
    for item in items {
        print(item.describe())
    }
    

    The output is:

    Dog named Rex
    City of Paris
    

    The contract is enforced

    If a type claims to conform but misses a requirement, or implements it with a different signature, the program doesn't compile. For example, a Dog whose describe() returns Int produces:

    error: type 'Dog' does not conform to protocol 'Describable'
    note: protocol requires function 'describe()' with type '() -> String'; add a stub for conformance
    note: candidate has non-matching type '() -> Int'
    

    Read it from the error to the notes: the error names the type that broke its promise, and the notes point at the missing or mismatched requirement. This strictness is the point. If the program compiles, every element of a [any Describable] is guaranteed to answer describe(). Likewise, putting a plain Int into that array is rejected, because Int doesn't conform.

    More to know

    • A protocol can be a parameter type. func show(_ d: some Describable) accepts any single conforming value. some means one specific conforming type chosen by the caller, while any allows a mix of types at run time.
    • Classes conform too. A subclass that overrides the method, with override, is picked when the object is a subclass instance, so an array holding a base object and a subclass object calls each one's own version.
    • One-line bodies. The braces are still required in { return "Dog named Rex" }. A single-expression body may leave out return.
    • Several protocols. A type can conform to many protocols, separated by commas: struct Dog: Describable, Equatable.

    Common mistakes

    • Writing a body in the protocol. The requirement is only a signature.
    • Forgetting : Describable. A Dog with a perfect describe() still can't go into the array if it never declared conformance.
    • Mismatched signature. A different return type or parameter list doesn't satisfy the requirement.
    • Forgetting the parentheses. The array holds instances, so write Dog(), not Dog.

    Key takeaways

    • A protocol lists requirements; conforming types provide the bodies.
    • Unrelated types can share one capability through a protocol.
    • [any Describable] mixes conforming types, and each call runs the right implementation.
    • The compiler refuses any type that does not fully meet its contract.
    Keep going with SwiftAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free