Introducing Swift 5.2
Introduced by Apple (on March 24, 2020), Swift 5.2 has handy features focused on improving the developer experience and providing additional language features. Some of the new language features seem to be oriented toward enhancing the functional programming style. Let's review these new features with some code examples.
Key path expressions as functions
This new feature allows developers to use key path expressions such as \Root.value
wherever (Root) -> Value
functions are allowed. Let's see it in action.
Let's create a Car
struct with two properties, brand
and isElectric
:
struct Car { let brand: String let isElectric: Bool }
Then, let's instantiate an array of Car
structs with two cars, one that's electric and one that's not electric:
let aCar = Car(brand: "Ford", isElectric: false) let anElectricCar = Car(brand: "Tesla", isElectric: true) let cars = [aCar, anElectricCar]
Now, if we want to filter this cars
array and get only the electric cars in it, we used to do it like this:
let onlyElectricCars = cars.filter { $0.isElectric }
We could also do it this way:
let onlyElectricCarsAgain = cars.filter { $0[keyPath: \Car.isElectric] }
Now, with Swift 5.2, we are able to do this more briefly:
let onlyElectricCarsNewWay = cars.filter(\.isElectric)
If you print the results, you will see that the output is the same:
print(onlyElectricCars) print(onlyElectricCarsAgain) print(onlyElectricCarsNewWay)
The output is as follows:
[__lldb_expr_5.Car(brand: "Tesla", isElectric: true)] [__lldb_e xpr_5.Car(brand: "Tesla", isElectric: true)] [__lldb_expr_5.Car(brand: "Tesla", isElectric: true)]
Note that this applies to more cases, such as map
, compactMap
, and wherever the (Root) -> Value
function is allowed.
The SE-0249 proposal contains all the details behind this change. For additional reference and the motivation behind the proposal, you can check the original document at https://github.com/apple/swift-evolution/blob/master/proposals/0249-key-path-literal-function-expressions.md.
Callable values of user-defined nominal types
This new feature allows values that have a method whose base name is callAsFunction
to be called like a function.
It is easier to explain this concept with a simple example. Let's create a struct called MyPow
that helps us to calculate the power of a number, given the base number:
import Foundation struct MyPow { let base: Double func callAsFunction(_ x: Double) -> Double { return pow(base, x) } }
Now, we can calculate the pow
of the base
just by doing the following:
let base2Pow = MyPow(base: 2) print(base2Pow.callAsFunction(3))
This print
statement will have the following result:
8.0
Now, with Swift 5.2, we can calculate the pow
of the base but using this method instead:
print(base2Pow(3))
This results in the same output:
8.0
The Swift SE-0253 proposal document contains all the details behind this change. For additional reference and the motivation behind the proposal, you can check the original document at https://github.com/apple/swift-evolution/blob/master/proposals/0253-callable.md.
Subscripts can now declare default arguments
When declaring a subscript, we are now able to assign a default value for an argument.
Let's see it in action with an example. We create a Building
struct that contains an array of String
representing floor names. We add a subscript to get the name of a floor with a given index. If the index doesn't exist, we want to get the default value, Unknown
:
struct Building { var floors: [String] subscript(index: Int, default default: String = "Unknown") -> String { if index >= 0 && index < floors.count { return floors[index] } else { return `default` } } } let building = Building(floors: ["Ground Floor", "1st", "2nd", "3rd"])
We can see in the following output that when we access index 0
with building[0]
, we return the value Ground Floor
:
print(building[0])
The console output is as follows:
Ground Floor
And in the following scenario, when we access the index 5
with building[5]
, we return the value Unknown
:
print(building[5])
The console output is as follows:
Unknown
This code example shows how we can make use of default arguments when using subscripts and how it can be helpful to tackle edge cases.
Lazy filtering order is now reversed
When working with a lazy array and filter, there is a new change in the order of the operations applied to the filters chain. Take a look at the following code:
let numbers = [1,2,3,4,5] .lazy .filter { $0 % 2 == 0 } .filter { print($0); return true } _ = numbers.count
In Swift 5.2, this code will print the following:
2 4
This is because the .filter { $0 % 2 == 0 }
statement is applied before the .filter { print($0); return true }
statement.
However, if we execute this code in a Swift version prior to 5.2, we will notice that the order will be the opposite. First, we will print all the numbers; then, we will filter and get only the even ones. The .filter
statements will execute from bottom to top.
This behavior will change again if we remove .lazy
from the code. Then, regardless of the Swift version, we will see the output as only 2
and 4
. The filters will be applied from top to bottom, as expected.
Important note
This change can break your code and the logic of your app. Make sure you review for any similar scenario when updating your code to Swift 5.2 or later.
New and improved diagnostics
In Swift 5.2, error messages have improved in quality and precision. In the previous version, the compiler tried to guess the exact location of an error by breaking down expressions into smaller pieces. But this method left some errors out there.
Now the compiler, when encountering failures while trying to infer a type, records the location of those elements. These recordings allow the compiler to detect the exact error later on if needed.
Let's see an example compiled in Swift 5.1 versus Swift 5.2 and the output on each version. Look at this code which contains an error:
enum Test { case a, b } func check(t: Test) { if t != .c { print("okay") } }
In Swift 5.2, we get a clear error in the exact location where it happens, and with an accurate reason:
error: Chapter 1.5.playground:14:12: error: type 'Test' has no member 'c' if t != .c { ~^
As you can see, the compiler is telling us that we are trying to use a member of the enum that doesn't exist, c
.
If we try to compile the same code in Swift 5.1, we will see a different (and incorrect) error message:
error: binary operator '!=' cannot be applied to operands of type 'Test' and '_' if t != .c { ~ ^ ~~~~~~
The improvements in the compiler errors make iOS developers' day-to-day debugging much more comfortable.
In this section, you have learned about the latest additions to the language and the improved diagnostics on Swift 5.2 with code examples. Now, let's jump into the features of Swift 5.3.