How to explicitly specialize a generic function in Swift

⋅ 4 min read ⋅ Swift

Table of Contents

What is an explicit specialization

Explicit specialization is a process where you explicitly specify a type of a generic function at a call site.

Here is an example of generic function which will create an array of type T, which required T to be a subclass of UIView.

func viewFactory<T: UIView>(numberOfView: Int) -> [T] {
return Array(repeating: T(), count: numberOfView)
}

If we want to produce a UIButton out of the viewFactory with explicit specialization, you have to specify a type directly like this viewFactory<UIButton>.

let buttons = viewFactory<UIButton>(numberOfView: 3)
// Cannot explicitly specialize a generic function

Too bad explicit specialization isn't supported in Swift[1], and you will get the following compile error.

Cannot explicitly specialize a generic function

Before going to a solution, I think it is good to learn about how the type arguments of a generic function are determined.

Type inference

The type arguments of a generic function are always determined via type inference. For a function to be inferrable, it must reference the type arguments in its method signature. So the compiler can infer the type arguments from the context.

You can reference the type arguments either in a method parameter or as a return type.

In the following example, the type argument is determined from parameter (t: T) and return type -> T.

func foo<T>(t: T)
func foo<T>() -> T

Infer from method's parameters

Here is a more concrete example of a generic function that infers the type from passing argument.

func greeeting<T: CustomStringConvertible>(_ t: T) {
print(type(of: T.self))
print("Hello, \(t.description)!")
}

greeeting("Sarunw")
// String.Type
//Hello, Sarunw!

greeeting(true)
// Bool.Type
// Hello, true!

The greeting function accepts any type (T) parameter that conforms to the CustomStringConvertible protocol. We pass a string and boolean into the function, and that's where T infer its type, Bool and String.

Infer from the return type

Another way that a generic function can infer its type is via a return type. An example of this is the one we use at the beginning of the article.

func viewFactory<T: UIView>(numberOfView: Int) -> [T] {
return Array(repeating: T(), count: numberOfView)
}

The compiler can infer the type from the context that the result is used. Here are two examples.

Variable type

A variable type that stores the function result use to determine the generic type argument.

Here is an example where the type of buttons use to determine the generic type. T will be UIButton in this case.

let buttons: [UIButton] = viewFactory(numberOfView: 3)

Method parameter type

If you pass a generic function result as an argument of any method, the method's parameter type is used to determine the generic type.

Here is an example where the parameter type of the methodThatsNeedButtons method is used to determine the generic type. T also be UIButton here.

func methodThatsNeedButtons(_ buttons: [UIButton]) {}

// 1
methodThatsNeedButtons(viewFactory(numberOfView: 3))

<1> viewFactory result is use as a argument for methodThatsNeedButtons method, so viewFactory can infer its type from method parameter type of methodThatsNeedButtons which is [UIButton].

Solution

A generic function that you might need to use explicit specialization is the one that infer its type from return type—the workaround for this by adding a parameter of type T as a way to inject type explicitly. In other words, we make it infer from method's parameters instead. Let's see how this change can help us directly specify the type of arguments.

I modified our viewFactory to support this explicit specialization.

// 1
func viewFactory<T: UIView>(_ t: T.Type, numberOfView: Int) -> [T] {
return Array(repeating: T(), count: numberOfView)
}

// 2
viewFactory(UIButton.self, numberOfView: 3)

<1> We add an argument of type T.Type. This makes our generic function can infer its type from the passing argument.
<2> Then, we can explicitly specify the type T by passing a type that you want this factory to create as an argument. In this case, a button.

You can easily support sarunw.com by checking out this sponsor.

Conclusion

Swift doesn't support explicit specialization, but we can achieve a similar effect using type inference. Apple also uses this technique in their API. One example is decode(_:from:).

func decode<T>(_ type: T.Type, from data: Data) throws -> T where T : Decodable

let user = try? JSONDecoder().decode(User.self, from: dataJson)

  1. At the time of writing (Swift 5), this isn't possible. But the door is still open in the future version of Swift https://github.com/apple/swift/blob/main/docs/GenericsManifesto.md#specifying-type-arguments-for-uses-of-generic-functions. ↩︎


Read more article about Swift or see all available topic

Enjoy the read?

If you enjoy this article, you can subscribe to the weekly newsletter.
Every Friday, you'll get a quick recap of all articles and tips posted on this site. No strings attached. Unsubscribe anytime.

Feel free to follow me on Twitter and ask your questions related to this post. Thanks for reading and see you next time.

If you enjoy my writing, please check out my Patreon https://www.patreon.com/sarunw and become my supporter. Sharing the article is also greatly appreciated.

Become a patron Buy me a coffee Tweet Share
Previous
How to show and hide a sidebar in a SwiftUI macOS app

Once the sidebar is collapsed, there is no way to get it back. Learn how to mitigate the situation.

Next
Spell checking in Xcode

Did you know that you have an option to enable spell checking in Xcode?

← Home