What is the purpose of declaring typealias for generic type parameter

Declaring a public typealias makes it possible to access the generic type parameter outside of the closed generic type.

For example if you declare a typealias WidgetFoo = Foo<Widget> and keep using WidgetFoo in other places it will be possible to access its T via WidgetFoo.Element(which refers to Widget) whereas you cannot access the generic type parameter E itself. This enables robust and refactoring friendly code - imagine you want to replace Widget with BetterWidget you only have to change one place (the type alias declaration) and no other because WidgetFoo.Element will then refer to BetterWidget.


Example code (provided by @Airspeed Velocity)

struct S<T> { typealias U = T }

typealias TA = S<Int>

let x: TA.T // 'T' is not a member type of 'TA' (aka 'S<Int>')
let y: TA.U

typealias and associatedtype mean that the values of type parameters can be hidden and become an implementation detail rather than the public interface of a type. This gets rid of an awful lot of noise when defining extensions, properties, methods, referencing superclasses/protocols etc.

Russ Bishop explains this really well with excellent examples here.


Here is a simple example of why you (sometimes) need to declare type aliases in a type that implements a protocol.

The Collection protocol is "generic" in that it contains "placeholder" types, known as associated types in Swift syntax. Collection includes two associated types which must be resolved in an implementing type: Element and Index.

In this example of a stack, we can start by declaring type aliases that will tell the compiler which concrete types we are going to use to satisfy the associated types from the protocol (itemT needs to derive from Hashable if it is to match the requirement for the Element associated type from the protocol):

class Stack<itemT : Hashable> : Collection
{
  typealias Element = itemT

  typealias Index = Int

  lazy var items: [itemT] = .init()

  func push(item: itemT)
  {
    items.append(item)
  }

  func pop() -> itemT?
  {
    guard let result = items.last,
          let lastIndex = items.lastIndex(of: result) else
    {
      return nil
    }

    items.remove(at: lastIndex)

    return result
  }
}

This now means that the compiler knows the Collection's Element type is going to be whatever we use as the itemT type when we use the stack. The Collection's Index type is going to be an Int, because that is the index type of the array we are using to hold the stack's items

Unfortunately, that is not enough to satisfy the requirements of the Collection protocol because there are a few "abstract" methods that also have to be implemented. So, we add those methods to our stack class. This will then tell the compiler the signatures of the required methods and allows the correct Fixits to be suggested, which, if we apply them, gives us the following code:

class Stack<itemT : Hashable> : Collection
{
  typealias Element = itemT

  typealias Index = Int

  lazy var items: [itemT] = .init()

  func push(item: itemT)
  {
    items.append(item)
  }

  func pop() -> itemT?
  {
    guard let result = items.last,
          let lastIndex = items.lastIndex(of: result) else
    {
      return nil
    }

    items.remove(at: lastIndex)

    return result
  }

  var startIndex: Int
  {
    return items.startIndex
  }

  var endIndex: Int
  {
    return items.endIndex
  }

  func index(after i: Int) -> Int
  {
    return items.index(after: i)
  }

  subscript(position: Int) -> itemT
  {
    return items[position]
  }
}

Finally, the class compiles! As a side effect of the compiler having now suggested correctly typed implementations of its "abstract" methods, it no longer needs us to tell it what the associated types from the protocol need to be linked to in our implementing type. So, we can now remove the type aliases from our code thus:

class Stack<itemT : Hashable> : Collection
{
  lazy var items: [itemT] = .init()

  func push(item: itemT)
  {
    items.append(item)
  }

  func pop() -> itemT?
  {
    guard let result = items.last,
          let lastIndex = items.lastIndex(of: result) else
    {
      return nil
    }

    items.remove(at: lastIndex)

    return result
  }

  var startIndex: Int
  {
    return items.startIndex
  }

  var endIndex: Int
  {
    return items.endIndex
  }

  func index(after i: Int) -> Int
  {
    return items.index(after: i)
  }

  subscript(position: Int) -> itemT
  {
    return items[position]
  }
}

Whether or not we need to declare type aliases for associated types often depends on the complexity of the associated types in a given protocol.

Tags:

Generics

Swift