The Swift Behind The Standard Library Preview Package ✨

If you're like me, you cannot wait to put your hands on the latest and greatest Swift features.

The most obvious example is probably Swift 5's introduction of Result: by the time it came out, tons of codebases had their own Result implementation already, all mine did.

There are multiple ways to test and use upcoming features without waiting for new Xcode (beta) releases: until recently the easiest one was via Swift Toolchains, however, while toolchains are good for experimenting, they cannot be used to release apps to the store.

Thanks to the Swift team announcement and release of the Standard Library Preview Package, this has now changed: with the Preview Package not only we can use and experiment new Swift features before any official release, but we can also ship apps with it!

As the Preview Package is like any other Swift Package, only Swift features that are purely additive and do not access to internal Swift APIs can be previewed this way:other Swift changes (behavioral, breaking, etc) cannot be previewed with this package.

Since this new package is open source, let's have a look at it!

Package Manifest

Let's understand its structure first, which we do from its Package.swift manifest:

let package = Package(
  name: "swift-standard-library-preview",
  products: [
    .library(
      name: "StandardLibraryPreview",
      targets: ["StandardLibraryPreview"]),
  ],
  dependencies: exports.map { $0.packageDependency },
  targets: [
    .target(
      name: "StandardLibraryPreview",
      dependencies: exports.map { $0.targetDependency }),
    
    .testTarget(
      name: "ExportTests",
      dependencies: ["StandardLibraryPreview"]),
  ]
)

Snippet from Standard Library Preview Package's Package.swift

From this definition we can see that the Preview Package offers one library product named StandardLibraryPreview, which exposes the only target in the package, also named StandardLibraryPreview, which has the same dependencies as the whole package.

These dependencies are declared in exports, an array of Export elements:

struct Export {
  var package: String
  var requirement: Package.Dependency.Requirement
  
  var name: String {
    let parts = package.split(separator: "-")
    let SE = parts[1].uppercased()
    let name = parts[2...].map { $0.capitalized }.joined()
    return "\(SE)_\(name)"
  }
  
  var packageDependency: PackageDescription.Package.Dependency {
    .package(url: "https://github.com/apple/\(package)", requirement)
  }
  
  var targetDependency: Target.Dependency {
    .product(name: name, package: package)
  }
}

Snippet from Standard Library Preview Package's Package.swift

Export tells us that every single Swift Evolution implementation that can be added to the Preview Package needs to:

  • be its own Swift Package
  • be hosted under Apple's Github organization (https://github.com/apple/...)
  • have a package name with at least two dashes
  • offer a library product with a name based on the package name

The last two requirements seems weird at first, but all becomes clear after looking at the actual exports array definition:

let exports = [
  Export(package: "swift-se0270-range-set", requirement: .upToNextMajor(from: "1.0.0")),
]

Snippet from Standard Library Preview Package's Package.swift.

At the moment there's just one Swift Evolution that can be previewed.

swift-se0270-range-set is the package name, while its vended library product name is SE0270_RangeSet:
the two dashes requirement is for Apple to prefix all the packages with swift-, followed by the Swift Evolution code, SE0270 in the example above, followed by another dash and then the name of the new swift feature, range-set in this case.

This naming convention makes it easy to manage all Preview Packages within the GitHub organization.

The manifest declaration is clever:
from now on only the exports definition needs to be updated, either when feature previews are added or removed, everything else will automatically work.

Every package target has its own folder, let's look at the StandardLibraryPreview folder next.

StandardLibraryPreview.swift

The only file in the this folder is StandardLibraryPreview.swift, and here it is in its entirety:

@_exported import SE0270_RangeSet

Snippet from Standard Library Preview Package's StandardLibraryPreview.swift.

From the manifest we already knew that SE0270_RangeSet is a library that we could import within the package, but what's this @_exported keyword?

@_exported

@_exported hasn't gone through Swift Evolution yet, as we can tell from the underscore prefix, it is possible that its behavior will change in the future.

The short answer stands within the README of the Preview Package: the Preview Package acts as an umbrella library, re-exporting each of the individual (Swift Evolution Implementations) packages.

The @_exported attribute lets us export symbols from another module as if they were from ours.

Let's imagine that we would like to expand a library with new functionalities:
we've always been able to can add and use extensions within our module, but what if we would like to wrap up everything in a new module/package?

At that point we would discover that the users of our new module will have access to all our public types declarations, but won't have access to anything from the original package, not even the public extensions that we've made to elements of the original package.

To fix this we have two solutions:

  • Ask our users to also import the original package, this way everything public, both from the original package and ours, is available to the users.
  • Use @_exported in our package: this way our users can import just our module, and they will automatically have access to both our public declarations and also to the ones from the original package. No further import necessary.

Pretty powerful and elegant, don't you think?

Overlay Libraries

If we create a package that extends another as described above, we've created what is know as an Overlay Package (or library, module, etc).

Apple uses such libraries within its SDKs for example to bring Swift-specific functionality to C-family libraries or frameworks.

Umbrella Libraries

The Standard Library Preview Package is not an overlay package, instead, it is what is know as an umbrella library, which only exports a subset of packages, without adding any other functionality.

MacOS's Cocoa framework is another example of umbrella framework, which is an umbrella of three other frameworks: AppKit, Foundation, and CoreData.

Conclusions

That's it! The whole Standard Library Preview Package contains only two swift files (excluding tests):

  • the Package.swift manifest
  • the StandardLibraryPreview.swift export file

I always appreciate when something complicated can be solved in such a simple way: - from the Preview Package user point of view (us!), we need to import StandardLibraryPreview, and we automatically get all the new Swift features - from the Preview Package maintainer point of view, adding/removing new Swift implementations is a matter of removing an entry from the Package.swift exports array, and then remove the associated @_exported import in the StandardLibraryPreview.swift (excluding tests)

Lastly, we've seen how the Preview Package uses @_exported, a powerful implementation detail that has not yet gone through Swift Evolution, but that plays a very important role in this package.

If you'd like to know more about @_exported and its possible future evolution, please see this discussion from the Swift Forums.

Have you ever seen @_export (or @_implementationOnly or ...) out in the wild? Do you or are you going to use them yourself? If so please let me know on Twitter! I'd love to see more of them 😃

Thank you for reading and stay tuned for more articles!

⭑⭑⭑⭑⭑

Further Reading

Explore Swift

Browse all