“This is the 16th day of my participation in the First Challenge 2022. For details: First Challenge 2022”
Swift is a statically typed language, which means that the type of each property, constant, and variable we declare needs to be specified at compile time. However, this usually doesn’t have to be done manually, but rather the compiler can calculate a wide range of type information on its own — thanks to the fact that Swift supports type inference.
For example, here we declare some constants — not specifying any type at all, because the compiler can infer this information from the assigned values:
For comparison, if we were to manually specify the type of each constant instead, the assignment above would look like this:
Thus, type inference plays an important role in making Swift’s syntax as lightweight as possible, not only for variable declarations and assignment of other types, but also for many other types of situations.
For example, here we define an enumeration describing various contacts and a function that lets us load an array of contact values belonging to a certain type:
Although we would normally refer to members of the above enumeration by specifying types and cases (such as contactkind.friend), due to type inference, we can refer to cases in contexts where the type is known entirely by omitting the name of the type — such as when calling our function above:
What’s really cool is that the “dot syntax” above doesn’t just apply to enumeration cases; it also applies to referencing any static property or method. For example, here we extend Foundation’s URL type with a static property that creates a URL pointing to the site:
Now, when calling any method that accepts a URL argument (such as the new composite-based URLSession API), we can simply refer to the above properties like this:
It’s really neat! However, while type inference is a very useful feature, in some cases we may need to specify some additional type information to achieve the results we want.
A very common example of this is when working with numeric types. When a numeric literal is assigned to a variable or constant, by default it is inferred to be of type Int — a perfectly reasonable default — but if we want to use other numeric types, such as Double or Float, we ‘will need to specify those types manually. Here are a few ways to do this:
Another situation where we might need to provide the compiler with additional type information is when calling a function with a common return type.
For example, here we extend the built-in Bundle type with a generic method that lets us easily load and decode any JSON files we Bundle in our application:
Now suppose that during our application development, until our real server and network code is up and running, we want to decode instances of the following user types from the bundled JSON file:
However, if we call the decodeJSONFile method like this, we end up with a compiler error:
That’s because the exact type we decode into any given JSON file depends on what the generic type T actually refers to at each calling site — and since we don’t provide any such information to the compiler, we end up with an error. In this case, the compiler has no way of knowing that we want to decode the User instance.
To solve this problem, we can use the same technique as above to specify different types of values, either to give our user constants an explicit type, or to use the AS keyword — like this:
However, if we were to call the decodeJSONFile method in a context where we know the required return type, we could again let Swift’s type inference mechanism find that information for us — as in this case, we’ve defined a wrapper structure called MockData, It has an attribute of user type, and we assign the result to:
That’s a quick overview of Swift’s type inference capabilities. It’s also important to point out that type inference does have computational costs associated with it, which fortunately happens entirely at compile time (so it doesn’t affect our application’s runtime performance), but it can still be good to keep in mind when working with more complex expressions. However, if we do encounter an expression that takes a long time for the compiler to figure out, we can always specify these types manually using one of the above techniques.