what are they and when should they be used

Type placeholders (type placeholders) is a new language feature introduced in Swift 5.6 (Xcode 13.3).
The concept itself is very simple – instead of specifying a specific type, we can put _ (type placeholder), which instructs the compiler to determine the fillable type itself.
In the following example, I am using a type placeholder for name
which is subsequently resolved in String
.
let name: _ = "Sarun"
When I first saw this feature, I didn’t immediately understand why I might need it at all, because Swift already has type inference, and I can write the previous code without specifying a type (or any placeholder) at all.
let name = "Sarun"
Why type placeholders are needed
As it turns out, the simple example above is not exactly what type placeholders were introduced for. Type placeholders are meant to be substituted for types with multiple types inside.
Here are some types that contain multiple types.
Function type:
(parameter type) -> return type
Dictionary type:
[key type: value type]
Generic types (generics): 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 types containing multiple other types is that Swift’s type inference can’t deal with such types; we are required to provide the whole type is explicitwhile actually need only part of this type. This becomes problematic when it comes to more complex types.
I think it will be easier to explain with examples.
Examples
Let’s look at a few examples of the intended use of type placeholders.
Functions
The first example is taken straight from the sentence. SE-0315. We are trying to create a string converter from Double.init
.
let stringConverter = Double.init
Insofar as Double.init
has many overloads, the compiler cannot guess the type of this expression.
Prior to Swift 5.6, you had to write this explicitly, providing more context for the type we were expecting.
// Аннотации типа переменной
let stringConverter: (String) -> Double? = Double.init
// Приведение типа через as
let stringConverter = Double.init as (String) -> Double?
We have provided both arguments and return types, but in this particular case, we only need to specify the type of the argument, since there is only one overload Double.init
which takes a String.
extension Double: LosslessStringConvertible {
public init?<S>(_ text: S) where S : StringProtocol
}
Type placeholders allow us to specify only the ambiguous part, leaving the rest up to the type inference process.
let stringConverter = Double.init as (String) -> ?
// или
let stringConverter: (String) -> ? = Double.init
Even though this example is listed in the proposal itself, I wouldn’t write stuff like this in my code. In this particular case, I prefer to clearly state all types.
In the examples above, we use ?
to indicate that the return value is optional, but you can remove the question mark from there (?
) and it won’t throw an error. Type placeholders can represent both optional and regular types.
Dictionaries
Type placeholders can be used instead of keys or values in dictionaries.
Below we will look at an example with a dictionary that stores cached images for each cell in a table view, and the row indexes are the keys.
var imageCache = [0: nil, 1: nil]
The compiler can infer the key (as Int
) but no value. The most he can do is Any?
.
We can instruct to put a type placeholder in place of the value and leave the key empty, since Swift is able to understand this part.
var imageCache: [: UIImage?] = [0: nil, 1: nil]
Again, I still prefer [Int: UIImage?]
this form.
Generic types
This is where I see the greatest benefit from introducing type placeholders. As functional programming becomes more and more popular, there may come a time when the types we use become a mess with more and more complex nesting, and explicitly specifying a type or leaving it empty (_) will be somewhat the same.
Consider the following tuple example:
let weirdTuple = (0, 1, Just(1).map(.description))
// (Int, Int, Publishers.MapKeyPath<Just<Int>, String>)
If we use it with an enum Result
from the example above as it is written now, we will get an error that the ‘Failure’ generic parameter cannot be inferred and we need to supply the entire type.
let result = Result.success(weirdTuple)
// Универсальный параметр 'Failure' не может быть выведен
In this case, leaving the type empty will make the code more readable.
let weirdTuple = (0, 1, Just(1).map(.description))
// Без заполнителя типа
let result = Result<(Int, Int, Publishers.MapKeyPath<Just<Int>, String>), Error>.success(weirdTuple)
// С заполнителем типа
let result = Result<, Error>.success(weirdTuple)
I can’t think of a more illustrative example than this one, but I hope you get the gist where it can be very helpful.
Again, type placeholders are not limited to being used only with types containing other types. It’s just that I personally find the maximum benefit from them here.
Here are some more examples of types containing placeholders.
Array<> // массив с заполнителем типа элемента
[Int: ] // словарь с заполнителем типа значения
() -> Int // тип функции, принимающий один аргумент-заполнитель и возвращающий 'Int'
(_, Double) // кортеж с заполнителем типа и 'Double'
? // необязательная обертка заполнителя типа
Conclusion
Type placeholders are a way to make life easier for us when we need to infer complex types (types that contain more than one type in their definition) where only a part is ambiguous.
You fill in the ambiguous part and still enjoy type inference with .
You can think of it as an underscore in the options list of a closure that you use to ignore options you don’t need.
let dict = [1: "One"]
dict.map { _, value in
print(value)
}
I think this feature should still be used with caution. I find it much harder to make a mistake when you explicitly state the type. Leaving users with any amount of ambiguity can lead to potential misunderstandings and errors.
In anticipation of the start of the iOS Developer course. Basic I want to invite everyone to a free lesson, in which we will look at how you can create a simple photo editor for iOS for simple image processing, work with filters and color tones. We will create the application interface using UIKit Autolayout.