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 extension
s 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 everythingpublic
, both from the original package and ours, is available to the users.
- Use
@_exported
in our package: this way our users canimport
just our module, and they will automatically have access to both ourpublic
declarations and also to the ones from the original package. No furtherimport
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!