Black Friday Sale - Save on EverythingAll videos. All books. Now 50% off.

Build your mobile development skills and save! Stay ahead of the rest with an Ultimate book & video subscription. Starting at just $149/year for Black Friday.

Home iOS & Swift Tutorials

What’s New in Swift 5.2

Swift 5.2 is now available as part of Xcode 11.4. In this article, you’ll get an overview of the changes you’ll see moving to Swift 5.2.

4.8/5 8 Ratings

Version

  • Swift 5, iOS 13, Xcode 11

Swift 5.2 is now available as part of Xcode 11.4. This article presents an overview of the changes you’ll see in this latest version.

Overall, Swift 5.2 is a minor release which is not necessarily a bad thing. It does bring many tweaks and minor improvements that will help the workflow of Swift developers. In this release, you’ll find:

  • Better diagnostics with more helpful error messaging, especially for SwiftUI.
  • New features that will simplify certain tasks.
  • Major bug fixes.

In many of the sections below, you’ll see references to the Swift Evolution proposals, like SE-0253, or the Swift bug reports, like SR-11298. You can explore these in greater depth using the links provided.

Also, follow along with the Playgrounds created for this article. Use the Download Materials button at the top or bottom to download them.

To start, you’ll explore the highest-profile feature: improvements to the error messages.

Improved Diagnostics and Error Messages

Do you always write perfect code on the first try? If not, you’ll love the improvements to the diagnostics engine in Swift 5.2! When you have an error in your code, the compiler gives you a more accurate description of the error and its location.

To be fair, the Swift compiler already did a good job reporting most errors in your code. The code below:

var str = "10"
let total = str + 5

…produced a clear error message:

A clear error from earlier Swift versions

The error message told you that you cannot use the ‘+’ operator to join a String and an Int. Depending on your intent, it also pointed you toward the action to fix the problem. If the goal was to add 5 to the number represented by the string, then you could fix the error like this:

var str = "10"
let total = Double(str)! + 5

At other times, however, the error messages weren’t all that helpful. This was especially true with errors related to type checking. Swift 5.2 addressed this issue by focusing on improvements to the type checker.

Easier Troubleshooting

Consider the following code for SwiftUI:

struct SquareView : View {
  @State var angle = 0.0
  
  var body: some View {
    VStack {
      TextField("Angle:", text: $angle)
      Rectangle()
        .rotation(Angle(degrees: angle))
        .frame(width: 100.0, height: 100.0)
    }
  }
}

Prior to Xcode 11.4, the error message you would have seen was this:

An ambiguous error in SwiftUI from older Swift versions

An ambiguous error in SwiftUI from older Swift versions

Not very helpful, eh? It was common to see unclear error messages such as this when working with SwiftUI. This made it that much harder to learn to use SwiftUI. Finding even the simplest typos became a process of tediously re-reading, removing or commenting out code to isolate the actual error.

Look again at the code block. Can you spot the actual problem? As a hint, it has nothing to do with converting a Double to a CGFloat!

Now open swiftui.playground in the starter folder. You’ll see that the compiler gives you a much more useful and actionable error message:

The improved error from Swift 5.2 and above

The improved error from Swift 5.2 and above

The error message points you to the correct line with the error and tells you the problem: You’re passing a binding to Double to a method that expects a binding to String.

Now that you know what to do to fix this error, replace the line indicated with this:

TextField("Angle", value: $angle, formatter: NumberFormatter.decimalFormatter)

Not Just SwiftUI

While you’ll notice better error messaging most often in SwiftUI, you’ll also see improvements in other Swift code. Open swift52.playground in the final project and you’ll find this code commented out:

let x: [Int] = [1, 2, 3, 4]
let w: UInt = 4

let filtered = x.filter { ($0 + w)  > 42 }

This code attempts to add an Int to a UInt without a cast and will not compile. Prior to Swift 5.2, the compiler displayed the following error:

error: binary operator '+' cannot be applied to operands of type 'Int' and 'UInt'

Now uncomment the code and you’ll see a more precise and helpful error message:

error: new-features.playground:34:22: error: cannot convert value of type 'UInt' to expected argument type 'Int'
_ = x.filter { ($0 + y) > 42 }
^
Int( )
Note: For a more detailed description of the improvements to the diagnostic engine, check out this blog article: New Diagnostic Architecture Overview.

Syntactic Sugar Additions

Swift already has quite a bit of syntactic sugar built into the language. Swift 5.2 adds to this by bringing two new features that certain developers will find very handy: Calling types as functions and using key path expressions as functions.

Note: Syntactic sugar changes the syntax of the language to make it easier to understand or more concise.

Calling Types as Functions

This new feature introduces statically callable values to Swift. But what does that mean? Well, it means that you can call classes or other structures as if they were functions.

To implement this feature in your code, you add a method named callAsFunction(_:) to the type. That’s it! There’s no step two.

With this addition, you can now call the value as a function. For example, take this type that represents a quadratic equation — a common mathematical function in the form of ax^2 + bx + c:

struct Quadratic {
  var a: Double
  var b: Double
  var c: Double

  func callAsFunction(_ x: Double) -> Double {
    a * pow(x, 2) + b * x + c
  }
}

You define your type as with any other value:

let f = Quadratic(a: 4, b: 4, c: 3)

Note that adding callAsFunction(_:) doesn’t prevent you from using the default init() method.

The choice of a mathematical type for this example is not a coincidence. Using syntax similar to mathematical notation provides the primary motivation for this feature.

let y = f(5)

As with any method, you can have multiple overrides for callAsFunction(_:) in your type. If you wanted to also calculate values for an array of Doubles and return the result as a new array, you could add a second implementation of callAsFunction(_:) like this:

func callAsFunction(_ xs: [Double]) -> [Double] {
  xs.map { callAsFunction($0) }
}

This code takes an array of Doubles and uses map() and the previous implementation of callAsFunction(_:) to produce an array with the results. Swift determines the appropriate method to call as it would for any other overridden function.

let z = f([5, 7, 9])

Cleaner Syntax

You could achieve the same result of this feature by adding a traditional method on this type. But the new syntax is cleaner, especially where there’s a single, obvious action for a value. In this example, the obvious action for the Quadratic type would be calculating the value of the equation for a given x. Similarly, a Parser will most often parse input as its primary function.

Machine Learning Application

This feature seems particularly focused on machine learning. You see this in the original proposal as it specifically mentions cleaning up the syntax for neural network applications. And it’s similar to Python and cross-compatible. All of this makes Swift even better suited for ML developers.

Note: For more information about this proposal, see this GitHub page: SE-0253: Callable values of user-defined nominal types

Key Path Expressions as Functions

With Swift 5.2, the language now allows \Root.value key path expressions wherever you could already use (Root) -> Value functions.

In code terms, where you could write:

orders.map { $0.email }

…you can now also write:

orders.map(\.email)

The current implementation limits this feature to key path literal expressions. You cannot currently write the following, though the discussion for this feature suggests future implementation:

let kp = \.email
users.map(kp)

However, you can accomplish something similar with different syntax:

let asEmail: (Order) -> String = \Order.email
orders.map(asEmail)

The first two features bring better functional abilities to Swift. You can now use functions in places that previously required blocks or closures. Since you now can pass a key path as a function, you can also pass a key path to a value that implements the new callAsFunction().

Note: For more information about this proposal, see this GitHub page: SE-0249: Key Path Expressions as Functions

Subscripts With Default Arguments

With Swift 5.2, you can now declare default arguments for custom subscripts when adding them for a type. For example, here’s a simple type that uses subscripts for multiplication:

struct Multiplier {
  subscript(x: Int, y: Int = 1) -> Int {
    x * y
  }
}

let multiplier = Multiplier()

This addition allows you to write code that specifies any number of available subscripts. Swift uses the provided default for any subscript not specified:

multiplier[2, 3]
multiplier[4]
Note: For more information about change, see this article at Swift.org: SR-6118: Mechanism to hand through #file/#line in subscripts

Major Bug Fixes

While new features get most of the attention in new releases, bug fixes are also critical. You’ll learn about these next.

Lazy Filters are Called in Order

When chaining calls to filter(_:) on a lazy sequence or collection, the filtering predicates are now called in the same order as eager filters. This bug fix is the one most likely to break existing code. For most collections, Swift calls filtering predicates in order. So this code:

let array = ["1", "2", "3"]
let filtered = array
  .filter { _ in
    print("A")
    return true
  }
  .filter { _ in
    print("B")
    return true
  }

_ = Array(filtered)

…will print:

A
A
A
B
B
B

Before Swift 5.2, evaluating a lazy sequence or collection evaluated in the reverse order. Take this example:

let lazyFiltered = array.lazy
  .filter { _ in
    print("A")
    return true
  }
  .filter { _ in
    print("B")
    return true
  }

_ = Array(lazyFiltered)

You would expect it to print:

A
B
A
B
A
B

…but it actually prints:

B
A
B
A
B
A

When compiled with Swift 5.2, the results print in the expected order. If you’ve written code that counts on the reversed execution, you’ll need to update your code to reflect the fixed behavior.

Note: For more information about this bug fix, see this article at Swift.org: SR-11841: Lazy filter runs in unexpected order

The remaining bug fixes in Swift 5.2 have less of an impact on existing code but are worth noting. You’ll want to know about these changes if you have worked around these issues in the past.

Default Values From Outer Scopes

The compiler now supports local functions whose default arguments capture values from outer scopes. This allows code such as this:

func outer(x: Int) -> (Int, Int) {
  func inner(y: Int = x) -> Int {
    return y
  }

  return (inner(), inner(y: 0))
}

Before Swift 5.2, the code above would not work.

Note: For more information about this bug fix, see this article at Swift.org: SR-2189: Nested function with local default value crashes

Warning When Passing Dangling Pointers

The compiler will now show a warning when you try to pass a temporary pointer argument that attempts to outlive the call. This would include code such as:

func generatePointer() {
  var number: Int8 = 0
  let pointer = UnsafePointer(&number)
}

This will result in a warning stating:

warning: initialization of 'UnsafePointer<Int8>' results in a dangling pointer<int8>
Note: Code that leaves a dangling pointer almost always indicates an error. A future version of the language may indicate this instead of just giving a warning.
For more information about this bug fix, see this article at Swift.org: SR-2790: Reject UnsafePointer initialization via implicit pointer conversion

Overridden Methods Can’t Use Incorrect Generics

Previously, you could write override a method with a generic signature incompatible with the base method’s generic signature. For example, you could write this:

protocol P { }

class Base {
  func doWork<T>(input: T) { }
}

class Derived: Base {
  override func doWork <T: P>(input: T) { }
}

In Swift 5.2, this code no longer compiles and will now throw an error.

Note: For more information about this bug fix, see this article at Swift.org: SR-4206: Override checking does not properly enforce requirements

Class-Constrained Protocol Extensions

A class-constrained protocol extension, where the extended protocol does not impose a class constraint, will now infer the constraint implicitly. Consider the following code:

protocol Foo {}

class Bar: Foo {
  var someProperty: Int = 0
}

extension Foo where Self: Bar {
  var anotherProperty: Int {
    get { return someProperty }
    set { someProperty = newValue }
  }
}

Here Foo does not impose a class constraint. But the compiler
infers this due to the Self: Bar constraint on Foo. This results in the setter becoming implicitly non-mutating, just as if Foo had a class constraint.

Note: For more information about this bug fix, see this article at Swift.org: SR-11298: Writable property declaration in a conditional-conforming protocol extension has incorrect mutability

Disambiguate Functions with Named Parameters

You can now use the as operator to disambiguate a call to a function with argument labels. Before, you could only do this for functions without label arguments. Consider these two functions:

func print(x: Int) { print("Int \(x)") }
func print(x: UInt) { print("UInt \(x)") }

Now you can differentiate the two functions using as with the following syntax:

(print as (Int) -> Void)(5) // Prints Int 5
(print as (UInt) -> Void)(5) // Prints UInt 5

There is one side effect to this change: You can no longer use a generic typealias to preserve the argument labels of a function reference using the as operator. An example of this would be:

typealias Magic<T> = T
(print as Magic)(x: 5)

This code will now result in a compiler error:

Extraneous argument label 'x:' in call

Instead, you must eliminate the argument from the call:

(print as Magic)(5)

This prints Int 5 like the first call above.

Note: For more information about this bug fix, see this article at Swift.org: SR-11429: Don’t look through CoerceExprs in markDirectCallee

Where to Go From Here

If you haven’t already, click the Download Materials button at the top or bottom of this article. Open these Playground files to explore many of the new changes and features for Swift 5.2.

While Swift 5.2 is not a major update, it does bring welcome changes and additions to the language. Nearly all developers will benefit from the improvements in diagnostics and error messaging. If they use machine learning in their projects, they’ll also appreciate the new type and key path features. And developers working with custom collections will welcome the addition of default subscript types.

If you would like to learn more about the Swift Evolution process, check out these links:

What are your thoughts about the new changes and features in Swift 5.2? Let us know in the forum discussion below!

Average Rating

4.8/5

Add a rating for this content

8 ratings

More like this

Contributors

Comments