Type Constraints for Generics in Swift

Arvindh Sukumar
Dispatch Swift
Published in
3 min readApr 16, 2016

--

One of the most powerful parts of Swift is its support for Generics. Using Generics, you can write code that is type-agnostic, meaning less code duplication.

But Generic code, as such, accepts any type. If you however want to constrain your code to accept only certain types, and that too only in certain conditions, what you need are Type Constraints.

Generics are made even more powerful and flexible by Type Constraints (and Where clauses). This article is my attempt at understanding where and how Type Constraints can be used, and the minutiae of their implementation. So here goes.

You can specify Type Constraints in the following cases:

  1. Implementations of Generic Types
  2. Generic functions
  3. Extensions of Generic Types, and
  4. Extensions of Generic Protocols

Generic Types & Functions

Constraints specified during the implementation of a Generic type limit the possible type parameters that can be used when creating an instance of that type.

The syntax for Type Constraints in Generic types as well as functions is the same:

<T, U where T: SomeClass, T: SomeProtocol, U: AnotherClass>

The above specifies that given two type parameters T & U, T should be a subclass of SomeClass as well as conform to SomeProtocol, and U should be a subclass of AnotherClass. The constraints that are therefore possible are:

  1. Class inheritance requirement, and
  2. Protocol conformance requirement

There is a third set of constraints that are possible — related to the protocol requirement’s associated type.

//Assuming that `AssociatedType` is the associatedtype of SomeProtocol:// Specify that the associated type is _derived_ from a specific class. Note the requirement of class - any other type cannot be used.
<T where T: SomeProtocol, T.AssociatedType:SomeClass>
// Specify that the associated type is a specific type (not necessarily a class).
<T where T: SomeProtocol, T.AssociatedType == SomeType>
// Specify that the associated types of two different types that conform to the same protocol, are of the same type
<T, U where T: SomeProtocol, U:SomeProtocol, T.AssociatedType == U.AssociatedType>

This is better explained in some examples:

Shorthand for class inheritance and protocol conformance requirements:

The above constraints can just be shortened as follows:

<T:SomeProtocol/Class, U:SomeProtocol/Class where ... >

But this only works when there is a single protocol conformance or class inheritance constraint. Other such constraints should still be mentioned as part of the Where clause.

Extensions of Generic Types

When you specify constraints while defining extensions for a Generic type, such extensions are only applied to instances of the type that satisfy those constraints.

The basic requirement for such extensions is that the constraints should be satisfiable, given the original constraints in the implementation of the type. For example, this will not work:

Also, extensions of generic types cannot have an inheritance clause — meaning, you cannot have a type conforming to a protocol via an extension while having constraints as well.

Extensions of Generic Protocols

Generic protocols that have associated types can be extended with additional constraints using those types. Such extensions will be available to all types that conform to the protocol and also conform to the associated type constraints.

Direct type parameter constraints aren’t available in this case. For instance, there is no way to outright specify that a protocol extension be available to, say, only subclasses of certain types. The extension availability is only inferred based on the associated type constraints.

Conclusion

As you can see, Type Constraints offer an additional layer of flexibility and type-safety over plain Generics. These are just some of the ways you can do this. Have I missed anything? Let me know!

--

--