Embrace Swift generics

Description: Generics are a fundamental tool for writing abstract code in Swift. Learn how you can identify opportunities for abstraction as your code evolves, evaluate strategies for writing one piece of code with many behaviors, and discover language features in Swift 5.7 that can help you make generic code easier to write and understand.

For the purpose of this talk, imagine to have an Animal protocol

  • Generics are here to abstract away the details of a specific type
  • if you find yourself writing overloads with repetitive implementations, it might be a sign to that you need to generalize
  • Start with concrete types, generalize when needed

Polymorphism

  • ability of abstract code to behave differently for different concrete types
  • allows one piece of code to have many behaviors depending on how the code is used

Different forms:

  • ad-hoc polymorphism
    • the same function call can mean different things depending on the argument type
    • Swift function overloading
  • subtype polymorphism
    • code operating on a supertype can have different behavior based on the specific subtype the code is using at runtime
    • Subclassing in Swift, where a class overrides a method of their superclass
  • parametric polymorphism - achieved using generics
  • Generic code uses type parameters to allow writing one piece of code that works with different types, and concrete types themselves are used as arguments

protocol

  • interface that represents capabilities
  • separates ideas from implementation details
  • abstraction tool that describes the functionality of conforming types
  • separates ideas from implementation details
  • each capability maps to a protocol requirement
  • the name of the protocol should represent the category of types we're describing

associatedtype

  • serves as a placeholder for a concrete type
  • associated types depend on the specific type that conforms to the protocol

Opaque type vs. underlying type

  • Opaque type - abstract type that represents a placeholder for a specific concrete type
  • underlying type - specific concrete type that is substituted in the opaque type
  • For values with opaque type, the underlying type is fixed for the scope of the value
  • generic code using the value is guaranteed to get the same underlying type each time the value is accessed
  • opaque type can be used both for inputs and outputs

Opaque types in Swift:

  • some Animal
  • <T: Animal>

Tips on writing generic interfaces (protocols or not)

some

Swift 5.6 and earlier:

func feed<A>(_ animal: A) where A: Animal

// 👆🏻👇🏻 Equivalents

func feed<A: Animal>(_ animal: A)

Swift 5.7 and later:

func feed(_ animal: some Animal)
  • we can express an abstract type in terms of the protocol conformance by writing some xxx
  • all three declarations above are identical, but the newer one is much easier to read and understand
  • the some keyword can be used in parameter and result types
  • with some, there is a specific underlying type that cannot vary

any

  • if we need to express an arbitrary type a protocol, for example to store multiple into an array, we can use any
  • with any, the underlying type can vary at runtime
  • any xxx is a single static type that has the capability to store any concrete X-conforming type dynamically, which allows us to use subtype polymorphism with value types
  • to allow for any required flexible storage, the any X type has a special representation in memory
    • you can think of this representation as a fixed box:
      • if the concrete type can fit within the box, it will be stored there
      • the box will store a pointer to the instance otherwise
  • the static type any X that can dynamically store any concrete X type is called an existential type
  • the strategy of using the same representation for different concrete types is called type erasure
  • the concrete type is said to be erased at compile time, and the concrete type is only known at runtime
  • type erasure eliminates the type-level distinction between different X-conforming instances, which allows us to use values with different dynamic types interchangeably as the same static type

some vs. any

  • the compiler can convert an instance of any X to some x by unboxing the underlying value and passing it directly to the some X parameter
    • new in Swift 5.7
someany
holds a fixed concrete typeholds an arbitrary concrete type
guarantees type relationshipserases type relationships

Guidelines:

  • use some by default
  • change to any when you need to store arbitrary values (arbitrary concrete types instances)

Missing anything? Corrections? Contributions are welcome 😃

Related

Written by

Cihat Gündüz

Cihat Gündüz

📱Indie iOS Developer, 🎬Content Creator for 👨🏻‍💻Developers. 👾Twitch-Streamer & ▶️YouTuber.

Federico Zanetello

Federico Zanetello

Software engineer with a strong passion for well-written code, thought-out composable architectures, automation, tests, and more.