What's New in Swift 5.6

Mar 14, 2022swiftswift-5.6

Swift 5.6 includes a number of enhancements to the type system, improved interaction with pointers, and adds the ability to run new plugin commands using the package manager.

Existential any

A plain protocol name in type context means an existential type. Existential types in Swift have significant limitations and performance implications. Some of their limitations are missing language features, but many are fundamental to their type-erasing semantics.

Existential types are also significantly more expensive than using concrete types. Because they can store any value whose type conforms to the protocol, and the type of value stored can change dynamically, existential types require dynamic memory unless the value is small enough to fit within an inline 3-word buffer. In addition to heap allocation and reference counting, code using existential types incurs pointer indirection and dynamic method dispatch that cannot be optimized away.

The language makes existential types too easy to reach for, especially by mistake. The cost of using existential types should not be hidden, and programmers should explicitly opt into these semantics.

This version makes existential types syntactically explicit in the language using the any keyword. Anywhere that an existential type can be used today, the any keyword can be used to explicitly denote an existential type.

Explicit any can only be applied to protocols and protocol compositions, or metatypes thereof; any cannot be applied to nominal types, structural types, type parameters, and protocol metatypes.

protocol P {}
protocol Q {}
struct S: P, Q {}

let p1: P = S() // 'P' in this context is an existential type
let p2: any P = S() // 'any P' is an explicit existential type

let pq1: P & Q = S() // 'P & Q' in this context is an existential type
let pq2: any P & Q = S() // 'any P & Q' is an explicit existential type

struct S {}
let s: any S = S() // error: 'any' has no effect on concrete type 'S'

func generic<T>(t: T) {
  let x: any T = t // error: 'any' has no effect on type parameter 'T'
}

Protocol CodingKeyRepresentable

This version adds a new protocol CodingKeyRepresentable to the standard library. Opting in to this protocol for the key type of a Dictionary will allow the Dictionary to encode/decode to/from a KeyedContainer.

public protocol CodingKeyRepresentable {
    var codingKey: CodingKey { get }
    init?<T: CodingKey>(codingKey: T)
}
// Same as stdlib's _DictionaryCodingKey
struct _AnyCodingKey: CodingKey {
  let stringValue: String
  let intValue: Int?

  init(stringValue: String) {
    self.stringValue = stringValue
    self.intValue = Int(stringValue)
  }

  init(intValue: Int) {
    self.stringValue = "\(intValue)"
    self.intValue = intValue
  }
}

struct ID: Hashable, CodingKeyRepresentable {
  static let knownID1 = ID(stringValue: "<some-identifier-1>")
  static let knownID2 = ID(stringValue: "<some-identifier-2>")

  let stringValue: String

  var codingKey: CodingKey {
    return _AnyCodingKey(stringValue: stringValue)
  }

  init?<T: CodingKey>(codingKey: T) {
    stringValue = codingKey.stringValue
  }

  init(stringValue: String) {
    self.stringValue = stringValue
  }
}

let data: [ID: String] = [
  .knownID1: "...",
  .knownID2: "...",
]

let encoder = JSONEncoder()
try String(data: encoder.encode(data), encoding: .utf8)

/*
{
  "<some-identifier-1>": "...",
  "<some-identifier-2>": "...",
}
*/

Type placeholders

This feature was originally discussed and accepted under the “placeholder types” title. The official terminology for this feature is now “type placeholders”.

Previously when Swift’s type inference is unable to work out the type of a particular expression, it requires the programmer to provide the necessary type context explicitly. However, all mechanisms for doing this require the user to write out the entire type signature, even if only one portion of that type is actually needed by the compiler.

enum Either<Left, Right> {
  case left(Left)
  case right(Right)

  init(left: Left) { self = .left(left) }
  init(right: Right) { self = .right(right) }
}

func makePublisher() -> Some<Complex<Nested<Publisher<Chain<Int>>>>> {
  /* ... */
}

let publisherOrValue = Either(left: makePublisher()) // Error: generic parameter 'Right' could not be inferred

let publisherOrValue = Either<Some<Complex<Nested<Publisher<Chain<Int>>>>>, Int>(left: makePublisher()) // Instead, we have to write out the full generic type

This version allows you to write types with designated type placeholders (_) which indicate that the corresponding type should be filled in during type checking. Effectively, type placeholders act as user-specified anonymous type variables that the type checker will attempt to solve using other contextual information.

let publisherOrValue = Either<_, Int>(left: makePublisher())

Unavailability Condition

Swift historically supported the #available condition to check if a specific symbol is available for usage, but not the opposite. Because the availability condition is not parsed as an expression, it cannot be negated with regular boolean operations (!/== false).

What if we’re only interested in negative portion of the check, we’ll leave behind an empty if branch.

if #available(iOS 13, *) {
  // no-op
} else {
  loadMainWindow()
}

This version introduces #unavailable check will eliminate the need to use the current workaround and makes it clear to the reader that the statement is checking for the lack of a specific version, eliminating the need to provide a comment explaining what that piece of code is trying to achieve.

if #unavailable(iOS 13, *) {
  loadMainWindow()
}