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. By Bill Morefield.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 2 of 3 of this article. Click here to view the first page.

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