How Does Swift Type Inference Work?
Swift is a statically typed programming language which means that the compiler needs to be able to figure out the type of each variable, constant, or property at compile time. However, thanks to Swift’s type inference, it is not always necessary to explicitly specify the type.
Today, I want to show you how type inference works and how it does help you to keep your code more lightweight.
Swift — a statically typed language
I like to have concrete examples when learning new things. So let’s compare a statically typed with a loosely typed language. Due to my experience in iOS and frontend web development, I use Swift (statically) and JavaScript (loosely).
Here you can see a completely valid function in JavaScript:
function jsFunc() {
var value = 32;
value = “Hi, guys!”;
}
In the function above, we simply create a variable that is initialized with a value of 32. In the next line, the same variable gets a new value of type string
assigned. You could even add another line and make an array out of that variable or assign an object to it. JavaScript doesn’t care. Because of that, you lose some protection against runtime errors.
Imagine you would call console.log(value.toLowerCase());
at the end of the function above. It would print out “hi, guys!”. Well, that’s no surprise. Next, add the log statement before changing the value in the second line of the function and call it while the value is still 32. Now, you get the following error:
Uncaught TypeError: value.toLowerString is not a function
The reason is that integers don’t have the function toLowerCase
. Again, that’s the expected behavior but it's error-prone when you use the same variable in different places and not just in the same function. You could accidentally assign a wrong value without the compiler warning you.
Now, let’s do the same in Swift:
func swiftFunc() {
var value: Int = 32
value = "Hi, guys!"
}
While in JavaScript the code was valid, in Swift it isn’t and we get a compiler error:
Cannot assign value of type ‘String’ to type ‘Int’
The reason is that a statically typed language allows no interchanging of types. If something is an integer at the beginning, it will stay an integer for the rest of its lifetime. That avoids nasty runtime errors. Sure, if you never do mistakes when you are programming, it doesn't matter. For the rest of us, it’s a nice feature to have that catches some of our shortcomings.
With the basics out of the way, let’s now look into explicit types first, before diving into type inference in Swift.
Explicitly typed assignments
So Swift needs to know the type at compile time. How does it work? First, let’s look at explicitly (or manually) specifying the type.
let stringLabel: String = "I am a string!"
var intValue: Int = 4
Here we have two assignments with explicit types. We tell Swift that the constant (let
) is of type String
and the variable (var
) is of type Int
. We can’t assign a double value to the variable intValue. That’s true for the initial and all other assignments throughout the lifetime of the variable. A constant on the other hand can only be assigned a value once but it again has to be of the type you specified (here String
).
Implicitly typed assignments (Swift type inference)
The process described above works fine and theoretically, you could write all your Swift code with explicit types. However, we programmers tend to keep things as short and lightweight as possible and use the great features our programming language provides. Hence, here is another example, that uses implicit types.
let stringLabel = "I am a string!"
var intValue = 4
Okay, it’s actually the same code and I just omitted the explicit types, but it does work! Swift sees the constant and the assignment and is able to infer that we assign a String
value to it and therefore the constant has to be a String
constant. The same is true for the variable where we assign an Int
value.
Important for the variable example is that we again can never reassign a value to the variable intValue that has a type other than Int
. Swift just figured out the type of the variable on its own. That doesn’t mean it changes anything regarding its strongly/statically typed nature.
Well, and that is what type inference in Swift is all about. Swift is able to detect types on its own.
But now you may ask, why we even need those explicit types when writing Swift code.
The limits of type inference
Even though the Swift compiler is pretty good at understanding what we want and able to infer types, it can not read our minds. Let’s say we want to create a Float
value. How can you do that? You could come up with the following solution:
let floatValue = 70.0
Sadly, Swift has Double
as its default floating-point type. Therefore, the code above would create a double and not a float constant.
We can solve this issue in different ways:
let explicitFloatValue: Float = 70.0
let castedFloatValue = Float(70.0)
let floatValueWithAsFloat = 70.0 as Float
You see, you need to provide Swift with more information to make it realize that you want this constant to be of type Float
. You could use an explicit type, which is what I would do in most cases. Alternatively, you can use the casting operator to cast the double value into a Float
or tell Swift that you want this value as a Float
.
Another example would be the following code:
func testFunc() {
let myValue
let someCondition = true if someCondition {
myValue = 2
} else {
myValue = 4
}
}
The code above doesn’t work. It’s unrelated to the fact that myValue is a constant. It’s just that you have only two options in Swift. You can either assign a value at the beginning so Swift can infer the type on its own or tell Swift what the type actually is.
To fix it, you could use again an explicit type:
func testFunc() {
let myValue: Int
let someCondition = true if someCondition {
myValue = 2
} else {
myValue = 4
}
}
Edit: I found another case where you need to provide the type explicitly. When you work optionals (values that can be nil or of the type you’ve provided) you may need to tell Swift that the current variable/constant/property can be set to nil or if you set it to nil right away.
let optionalValue = nil // Error
let workingOptional: String? = nil // Does worklet nonOptional = "Hello" // Constant of type String
var beCareful = nonOptional // Variable of type String (not String?)
var better: String? = nonOptional // Variable of type String?
beCareful = nil // Error, Swift thinks beCarful is of type String
better = nil // Does work, Swift knows its either String or nil
Bonus tip
Swift never converts types implicitly. That means you cannot do arithmetic operations or something similar without casting a value.
let doubleValue = 70.0
let floatValue: Float = 30.0
let sum = doubleValue + floatValue // This fails
The code above wouldn’t compile and you’d get the following error:
Binary operator ‘+’ cannot be applied to operands of type ‘Float’ and ‘Double’
In other programming languages, this may have worked, because they implicitly convert the values. But Swift wants to help you and avoid unexpected behavior, so you have to explicitly tell Swift how the conversion should look like. Hence, to make it work, you’d need to cast one of those values to the other.
Conclusion
Swift’s type inference allows you to reduce the number of characters you need to write. It’s a neat tool, that you should have in mind and use to keep your code lightweight.
Thanks to it’s statically typed nature, Swift also protects us against a lot of runtime errors that could happen because we accidentally reassign values of different types than intended.
That’s it for today and as always be a professional, guys!