Simplifying Generic Protocols in Swift 5.7

KD Knowledge Diet
3 min readApr 10, 2024

Swift, with its powerful generics system, allows developers to write flexible and reusable code. Combining generics with protocol-oriented programming can lead to powerful implementations while minimizing code duplication. However, in previous Swift versions, using generic protocols with associated types or `Self` requirements often resulted in compiler errors. Swift 5.7, introduced as part of Xcode 14, brings new features to address these issues and simplify working with generic protocols.

Opaque Parameter Types

One common issue when working with generic protocols is that as soon as a protocol defines an associated type, the compiler starts placing limitations on how that protocol can be referenced. For example, consider a `Group` protocol with an associated type `Item`:

protocol Group {
associatedtype Item
var items: [Item] { get }
var users: [User] { get }
}

With this associated type, you couldn’t reference the `Group` protocol directly in code unrelated to `Item`, as it would result in a compiler error:

func namesOfUsers(addedTo group: Group) -> [String] {
// Error: Protocol 'Group' can only be used as a generic constraint
// because it has Self or associated type requirements.
group.users.compactMap { user in
isUserAnonymous(user) ? nil : user.name
}
}

Prior to Swift 5.7, you would need to make your function generic and use the `Group` protocol as a type constraint:

func namesOfUsers<T: Group>(addedTo group: T) -> [String] {
// …
}

Swift 5.7 introduces the `some` keyword, which can be used to define opaque return types and, in this case, to specify that a parameter should accept “some” conforming type. You can now make your function accept “some `Group`” as its input:

func namesOfUsers(addedTo group: some Group) -> [String] {
// …
}

The compiler will automatically infer the concrete type passed to the function at each call site, simplifying the code.

Primary Associated Types

In some cases, you may want to add additional requirements to a parameter beyond protocol conformance. For instance, consider a `BookmarksController` class with a method `bookmarkArticles` that accepts an array of articles:

class BookmarksController {
func bookmarkArticles(_ articles: [Article]) {
// …
}
}

But what if you want to accept any collection of articles, not just arrays? Prior to Swift 5.7, you would use generic type constraints:

class BookmarksController {
func bookmarkArticles<T: Collection>(
_ articles: T
) where T.Element == Article {
// …
}
}

Swift 5.7 allows you to specify the expected element type directly after the protocol name, simplifying the declaration:

class BookmarksController {
func bookmarkArticles(_ articles: some Collection<Article>) {
// …
}
}

You can use this syntax to specify the expected element type for collections, making your code more concise and expressive.

Existentials and the `any` Keyword

Swift 5.7 introduces the `any` keyword, allowing you to refer to protocols as existentials. This can be especially useful when you need to work with heterogeneous collections of elements conforming to a protocol. Consider a `ContentSelectionController` that works with various types conforming to a `ContentItem` protocol:

class ContentSelectionController {
var selection = [IndexPath: any ContentItem]()
private let bookmarksController: BookmarksController
func bookmarkSelection() {
bookmarksController.bookmark(selection.values)
// …
}
}

However, this introduces a new challenge since the `bookmark` method in `BookmarksController` expects a collection with elements of the same type. You can resolve this by using the `any` keyword in method declarations:

class BookmarksController {
func bookmark(_ items: some Collection<any ContentItem>) {
// …
}
}

Now, you can work with heterogeneous collections without sacrificing type safety, thanks to the combination of `some` and `any`.

Conclusion

Swift 5.7 enhances the language by simplifying the usage of generic protocols with associated types and `Self` requirements. The introduction of the `some` and `any` keywords allows developers to write more concise and expressive code when working with protocols and generics. These features reduce the need for complex generic type constraints and make Swift’s generics system more accessible and user-friendly. As you adopt Swift 5.7, you’ll find it easier to write flexible and reusable code while maintaining type safety. Happy coding!

--

--

KD Knowledge Diet

Software Engineer, Mobile Developer living in Seoul. I hate people using difficult words. Why not using simple words? Keep It Simple Stupid!