What's New in Swift 5.7

Jul 29, 2022swiftswift-5.7

Swift 5.7, which is included in beta versions of Xcode 14, introduces some great additions to the Swift language that include a whole spectrum of changes, from smaller syntax improvements to larger generics and concurrency updates.

Extensions on bound generic types

Allow extending bound generic types using angle-brackets for binding type arguments, or using sugared types such as [String] and Int?.

extension Array where Element == String { /* ... */ }
extension Array<String> { /* ... */ }
extension [String] { /* ... */ }

Opaque result types with limited availability

Allow functions to use multiple if #available conditions to return different types based on their dynamic availability and safely fall back to a return type with no availability restrictions if none of the availability conditions are met.

func test() -> some Shape {
  if #available(macOS 100, *) {
    return Rectangle()
  } else if #available(macOS 99, *) {
    return Square()
  }
  return self
}

Because the return type must be decidable without running the code in the function, mixing availability conditions with other conditions (such as if, guard, or switch) removes this special power and requires returns in the if #available to return the same type as the rest of the function.

func test() -> some Shape {
  if cond {
    /* ... */
  } else if #available(macOS 100, *) {
    return Rectangle()
  }
  return self
}

Regex Literals

The introduction of regex literals written using forward slashes /.../, instantly recognizable as a regex dates back to 1969’s ed. The compiler will parse the contents of a regex literal, diagnosing any errors at compile time. The capture types and labels are automatically inferred based on the capture groups present in the regex.

let regex = /(?<identifier>[[:alpha:]]\w*) = (?<hex>[0-9A-F]+)/
// regex: Regex<(Substring, identifier: Substring, hex: Substring)>

A regex literal may be surrounded by an arbitrary number of balanced number signs #. This changes the delimiter of the literal, and therefore allows the use of forward slashes without escaping.

let regex = #/usr/lib/modules/([^/]+)/vmlinuz/#
// regex: Regex<(Substring, Substring)>

Constrained Existential Types

Existential types will be augmented with the ability to specify constraints on their primary associated types. When an existential type appears with such constraints, they will be converted into same-type requirements.

protocol P<T, U, V> { }
var xs: [any P<B, N, J>] // "Equivalent" to [any P] where P.T == B, P.U == N, P.V == J

Implicitly Opened Existentials

To open an existential, the argument must be of existential type any P or existential metatype any P.Type and must be provided to a parameter whose type involves a generic parameter that can bind directly to the underlying type of the existential.

protocol P {
  associatedtype A
  func getA() -> A
}

func openSimple<T: P>(_ value: T) { }

func testOpenSimple(p: any P) {
  openSimple(p) // okay, opens 'p' and binds 'T' to its underlying type
}

buildPartialBlock for result builders

Swift introduces a new block-building approach similar to building heterogeneous lists. Instead of calling a single method to build an entire block wholesale, this approach recursively builds a partial block by taking one new component at a time, and thus significantly reduces the number of overloads.

@resultBuilder
enum Builder {
  static func buildPartialBlock(first: Component) -> Component
  static func buildPartialBlock(accumulated: Component, next: Component) -> Component
}

When buildPartialBlock(first:) and buildPartialBlock(accumulated:next:) are both defined, the result builder transform will turn components in a block into a series of calls to buildPartialBlock, combining components from top to bottom.

// Original
{
  expr1
  expr2
  expr3
}

// Transformed
{
  let e1 = Builder.buildExpression(expr1)
  let e2 = Builder.buildExpression(expr2)
  let e3 = Builder.buildExpression(expr3)
  let v1 = Builder.buildPartialBlock(first: e1)
  let v2 = Builder.buildPartialBlock(accumulated: v1, next: e2)
  let v3 = Builder.buildPartialBlock(accumulated: v2, next: e3)
  return Builder.buildFinalResult(v3)
}

Type inference from default expressions

Swift allow type inference for generic parameters from concretely-typed default parameter values (referred to as default expressions in the proposal) when the call-site omits an explicit argument.

struct Box<F: Flags> {
  init(flags: F = DefaultFlags()) {
    /* ... */
  }
}

Box() // F is inferred to be DefaultFlags
Box(flags: CustomFlags()) // F is inferred to be CustomFlags

Lightweight same-type requirements

A new syntax for declaring a protocol conformance requirement together with one or more same-type requirements on the protocol’s primary associated types. This new syntax looks like the application of a concrete generic type to a list of type arguments, allowing you to write Sequence<String> or Sequence<[Token]>.

func readSyntaxHighlightedLines(_ file: String) -> some Sequence<[Token]> {
  /* ... */
}

func concatenate<S : Sequence<String>>(_ lhs: S, _ rhs: S) -> S {
  /* ... */
}

Shorthand for optional binding

Swift introduces a shorthand syntax for optional binding when shadowing an existing variable. We can omit the right-hand expression, and allow the compiler to automatically shadow the existing variable with that name, these optional bindings are much less verbose, and noticeably easier to read and write.

let someLengthyVariableName: Int? = 10
let anotherImportantVariable: Int? = 20

if let someLengthyVariableName, let anotherImportantVariable {
  /* ... */
}

Concurrency in Top-level Code

This feature will only apply when the top-level code is an asynchronous context. As a synchronous context, the behavior of top-level code does not change. In order trigger making the top-level context an asynchronous context, using the presence of an await in one of the top-level expressions.

func doAsyncStuff() async {
  /* ... */
}

let countCall = 0

let myClosure = {
  await doAsyncStuff() // nested `await` within a function declaration does not trigger async top-level
  countCall += 1
}

await myClosure() // This `await` will trigger an async top-level

Opaque Parameter Declarations

Swift extends the use of the some keyword to parameter types for function, initializer, and subscript declarations. As with opaque result types, some P indicates a type that is unnamed and is only known by its constraint: it conforms to the protocol P. When an opaque type occurs within a parameter type, it is replaced by an (unnamed) generic parameter.

func f(_ p: some P) {
  /* ... */
}

// Equivalent to a generic function described as follows
func f<_T: P>(_ p: _T) {
  /* ... */
}

Unavailable From Async Attribute

Extending @available to accept a noasync availability kind. The noasync availability kind is applicable to most declarations, but is not allowed on destructors as those are not explicitly called and must be callable from anywhere.

@available(*, noasync)
func doSomethingNefariousWithNoOtherOptions() { }

@available(*, noasync, message: "use our other shnazzy API instead!")
func doSomethingNefariousWithLocks() { }

func asyncFun() async {
  // Error: doSomethingNefariousWithNoOtherOptions is unavailable from
  //        asynchronous contexts
  doSomethingNefariousWithNoOtherOptions()

  // Error: doSomethingNefariousWithLocks is unavailable from asynchronous
  //        contexts; use our other shanzzy API instead!
  doSomethingNefariousWithLocks()
}

Structural opaque result types

An opaque result type may be used as the result type of a function, the type of a variable, or the result type of a subscript. In all cases, the opaque result type must be the entire type. Swift 5.7 recommends lifting that restriction and allowing opaque result types in “structural” positions.

// express a function that might fail to produce an opaque result type
func f0() -> (some P)? { /* ... */ }

// use an opaque result type as one of several return values
func f1() -> (some P, some Q) { /* ... */ }

// return a lazily computed opaque result type
func f2() -> () -> some P { /* ... */ }

// embed an opaque result type into a larger structure
func f3() -> S<some P> { /* ... */ }