BT

Facilitating the Spread of Knowledge and Innovation in Professional Software Development

Write for InfoQ

Topics

Choose your language

InfoQ Homepage News Swift 5.1 Brings Module Stability, Opaque Return Types, Property Wrappers and More

Swift 5.1 Brings Module Stability, Opaque Return Types, Property Wrappers and More

This item in japanese

Bookmarks

While module stability is by far the most impactful new feature in Swift 5.1, the latest version of Apple's language includes a number of new language constructs, such as property wrappers and opaque return types, and a number of standard library extensions.

As you may recall, Swift first reached ABI stability with version 5.0. ABI stability aims to make it possible for code compiled with different Swift compilers to work together, thus bringing some level of binary compatibility to the language in the face of changes to the language syntax. Two major implications of ABI stability are the possibility of not bundling a copy of the Swift runtime with each shipping application, and the possibility of enabling the distribution of binary frameworks by third-party developers and Apple itself. This goal was accomplished only partially with Swift 5.0, so apps did not need to include the Swift runtime anymore starting with iOS 12.2, but developers had to wait until Swift 5.1 and iOS 13 for the creation of distributable binary frameworks to become real.

Apple paid special attention to preventing the requirements of ABI stability from impairing any performance improvements that future, non-ABI-compliant language features might provide. This mechanism is called library evolution in Swift jargon and essentially boils down to allowing frameworks to opt-out of ABI stability on a per-type basis. This would make it possible to take advantage of given compile-time optimizations at the expense of binary compatibility. It is also important to note that non-standalone frameworks, i.e. those that are bundled within an app, are always compiled with the full set of available optimizations, since ABI stability is not relevant for them.

At the language level, Swift 5.1 brings a number of significant new features. Property wrappers aim to generalize the possibility of defining implementation patterns for properties and provide them through libraries. For example, Swift included lazy and @NSCopying property modifiers that were embedded into the language itself. The compiler knew how to translate those modifiers into the corresponding property implementation, but this mechanism was not flexible and made the compiler itself more complex. So, Swift 5.1 introduces a special class of generics that are marked with @propertyWrapper. A property wrapper provides the storage for that property it wraps and a wrappedValue property the provides the actual implementation of the wrapper. For example, this is how the lazy semantics could be implemented using a property wrapper:

@propertyWrapper
enum Lazy<Value> {
  case uninitialized(() -> Value)
  case initialized(Value)

  init(wrappedValue: @autoclosure @escaping () -> Value) {
    self = .uninitialized(wrappedValue)
  }

  var wrappedValue: Value {
    mutating get {
      switch self {
      case .uninitialized(let initializer):
        let value = initializer()
        self = .initialized(value)
        return value
      case .initialized(let value):
        return value
      }
    }
    set {
      self = .initialized(newValue)
    }
  }
}

Opaque return types address the problem of type-level abstractions for return types. This feature enables to abstract a return value whose concrete type is determined by the implementation and not by the call syntax, as it usually is case with generics. For example, a function's implementation could return a collection without disclosing what concrete collection it is. This could be represented by the following code snippet:

protocol Collection {
  func operation1()
  func operation2()
}

protocol ApplicationObject {

  // a collection used by this type
  associatedtype Collection: Collections.Collection

  var collection: Collection { get }
}

struct Invoice: ApplicationObject {
  var items: some Collection {
    return ...
  }
}

This is similar in nature to what is known in Objective-C as class clusters of which the most compelling example is the class (cluster) NSString.

Swift 5.1 also aims to remove a nuisance when using default values for struct properties. In such cases, the Swift compiler was not able to generate a default initializer that took into account the fact that some properties of the struct had a default value, thus forcing developers to specify all properties in the initializer call. In Swift 5.1, if a struct property has a default value, the compiler will synthesize a default initializer that will properly match those defaults in its signature. For example:

struct Person {
  var age: Int = 0
  var name: String

// default compiler-synthesized initializer:
// init(age: Int = 0, name: String)

}

let p = Person(name: "Bill")

// Swift 5.0 obliged to specify both arguments:
// let p = Person(age: 0, name: "Bill")

A less powerful, although not less useful new feature in Swift 5.1 is the implicit return that is assumed in one-line closures, similarly to what is allowed in other languages. For example, you can now write:

func sum(_ a: Int, _ b: Int) -> Int { a + b }

Other new language features in Swift 5.1 are key path member lookup, aimed at extending the usual dot syntax to arbitrary names that are resolved at runtime in a type-safe manner, and static subscript and class subscript, which extend the possibilities of defining subscripts which were previously only allowed as class members. Other improvements in Swift 5.1 are found in the Standard Library, including ordered collection diffing, contiguous strings, additions to SIMD support, and more.

Rate this Article

Adoption
Style

BT