Swift Type placeholder: What is it and when to use it

⋅ 5 min read ⋅ Swift

Table of Contents

Type placeholders is a new language feature introduced in Swift 5.6 (Xcode 13.3).

The concept is straightforward. Type placeholders allow us to write types with type placeholders (_) which directs the compiler to infer the type where _ is used.

In the following example, I use type placeholders for name, which will resolve to String.

let name: _ = "Sarun"

This is a feature that left me wondering why do I need this when I first saw it. Swift already has type inference, and I can write the previous code without type (or type placeholder).

let name = "Sarun"

Why do I need Type placeholders

Turn out this simple case isn't what type placeholders were introduced for. Type placeholders mean to be used for a type with multiple types in it.

Here are some types that contain multiple types.

Function Type:

(parameter type) -> return type

Dictionary Type:

[key type: value type]

Generic Types: Any type with more than one generic type, e.g. Result.

public enum Result<Success, Failure> where Failure : Error {
case success(Success)
case failure(Failure)
}

The problem with type with multiple types is when Swift's type inference can't infer the type; it requires us to provide all the type explicitly while, in fact, only one portion of that type is needed. This becomes problematic in cases where a complex type is involved.

I think it is easier to understand with examples.

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

Sponsor sarunw.com and reach thousands of iOS developers.

Examples

Let's see some examples of type placeholders.

Function Type

The first example comes from the SE-0315 proposal. We try to create a string converter from a Double.init.

let stringConverter = Double.init

Since there are many overloads over Double.init, a compiler can't refer to the type of this expression.

Before Swift 5.6, you have to be explicit about this and provide more context for the type we expected.

// Variable type annotations
let stringConverter: (String) -> Double? = Double.init

// Type coercion via as
let stringConverter = Double.init as (String) -> Double?

We provided both argument and return types, but for this case, we only need to clarify the argument type since there's only one Double.init overload that accepts a String.

extension Double: LosslessStringConvertible {
public init?<S>(_ text: S) where S : StringProtocol
}

Type placeholders allow us to specify the ambiguous part and leave the rest to the type inference to figure out.

let stringConverter = Double.init as (String) -> _?
// or
let stringConverter: (String) -> _? = Double.init

Dictionary Type

You can use type placeholders in place of dictionary keys or values.

Here is an example of using a dictionary to keep image caches for each table view cell where the key is a row index.

var imageCache = [0: nil, 1: nil]

A compiler can infer the key as Int but not the value. Its best guess would be Any?.

We can specify the value with type placeholders and leave the key blank since Swift can figure out that part.

var imageCache: [_: UIImage?] = [0: nil, 1: nil]

Generic Types

This is the area where I can see the value of type placeholders. Since functional programming has become more popular, there might be a time when type becomes a complex-nested mess, and being explicit on the type or leaving it blank (_) are somewhat the same.

Here is an example of imaginary tuple.

let weirdTuple = (0, 1, Just(1).map(\.description))
// (Int, Int, Publishers.MapKeyPath<Just<Int>, String>)

If we use this as a success case for Result enum like this, we will get an error that the generic parameter 'Failure' could not be inferred, and we need to provide the whole type.

let result = Result.success(weirdTuple)
// Generic parameter 'Failure' could not be inferred

In this case, leaving the type blank might be more readable.

let weirdTuple = (0, 1, Just(1).map(\.description))

// Without type placeholders
let result = Result<(Int, Int, Publishers.MapKeyPath<Just<Int>, String>), Error>.success(weirdTuple)

// With type placeholders
let result = Result<_, Error>.success(weirdTuple)

Again, type placeholders are not constrained to a type with multiple types. I just find it more valuable there.

Here are some more examples of types containing placeholders.

Array<_> // array with placeholder element type
[Int: _] // dictionary with placeholder value type
(_) -> Int // function type accepting a single type placeholder argument and returning 'Int'
(_, Double) // tuple type of placeholder and 'Double'
_? // optional wrapping a type placeholder

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

Sponsor sarunw.com and reach thousands of iOS developers.

Conclusion

Type placeholders are the way to enjoy type inference for complex types (a type that contains more than one type in its definition) where only part of the type is ambiguous.

You fill in the ambiguous part and can still enjoy the type inference with _.

You can think of this as an underscore in closure's parameter list that you use to ignore the parameters you don't care about.

let dict = [1: "One"]
dict.map { _, value in
print(value)
}

I think this is the kind of feature that you should use with caution. I find it hard to go wrong when you be explicit about the type. Leaving users guessing might open up an opportunity for potential misunderstanding and bugs.


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 change the keyboard shortcut for IntelliSense in VS Code

I couldn't initiate IntelliSense for a long time because I couldn't find any Keyboard shortcuts for "IntelliSense". If you are in the same situation, I have a solution for you.

Next
How to use async/await in Flutter

Many things can go wrong when you write an asynchronous function in Dart. Let's learn how to use Future, async, and await properly.

← Home