How to explicitly specialize a generic function in 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.
You can easily support sarunw.com by checking out this sponsor.
Debug 10x faster with Proxyman: Your ultimate tool to capture HTTPs requests/ responses, natively built for your iPhone and macOS. Special deal for Black Friday: Get 30% off for all Proxyman licenses with code “BLACKFRIDAY2024”.
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.
Debug 10x faster with Proxyman: Your ultimate tool to capture HTTPs requests/ responses, natively built for your iPhone and macOS. Special deal for Black Friday: Get 30% off for all Proxyman licenses with code “BLACKFRIDAY2024”.
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)
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 ShareHow 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.
Spell checking in Xcode
Did you know that you have an option to enable spell checking in Xcode?